Why RSVP Deferred produces an error when the promise is called twice?
It seems that there is a difference between deferred.promise.then().finally() and deferred.promise.then(); deferred.promise.finally(). Why?
RSVP.on('error', function(reason) {
console.log('Error: ' + reason);
});
var deferred = RSVP.defer();
var deferred2 = RSVP.defer();
var deferred3 = RSVP.defer();
var promise3 = deferred3.promise;
deferred.promise.then(function() {
console.log('Resolved');
}, function() {
console.log('Rejected');
}).finally(function() {
console.log('Finally');
});
deferred2.promise.then(function() {
console.log('Resolved2');
}, function() {
console.log('Rejected2');
});
deferred2.promise.finally(function() {
console.log('Finally2');
});
promise3 = promise3.then(function() {
console.log('Resolved3');
}, function() {
console.log('Rejected');
});
promise3.finally(function() {
console.log('Finally3');
});
deferred.reject('Reject!');
deferred2.reject('Reject2!');
deferred3.reject('Reject3!');
<script src="https://cdnjs.cloudflare.com/ajax/libs/rsvp/4.8.1/rsvp.js"></script>
EDIT: I found out how to fix the issue. See the Deferred3 in the code.
I found that promise.then() (and other methods) returns a modified promise so you have to chain the then, finally, catch... methods or you have to save the promise each time.
RSVP.on('error', function(reason) {
console.log('Error: ' + reason);
});
var deferred = RSVP.defer();
var promise = deferred.promise;
promise = promise.then(function() {
console.log('Resolved');
}, function() {
console.log('Rejected');
});
promise.finally(function() {
console.log('Finally');
});
deferred.reject('Reject!');
<script src="https://cdnjs.cloudflare.com/ajax/libs/rsvp/4.8.1/rsvp.js"></script>
Related
I have a chain of promises as a function in my app. Each of my service functions returns a deferred.promise;
Considering the following scenario I have where main getUser service calls getUserPreferences and getUserFavourites asynchronously, the console.log after resolving getUserData is being resolved before getUserFavourites even responds! Shouldn't the promise in getUserData be resolved once getUserFavourites responds?
In fact 'got all user data' from the console.log is in the console before getUserFavourites is called. Literally straight after getUser responds almost like getUserData().then( only resolves the top level promise and make the underlying 2 asynchronous...
What am I doing wrong here?
var user = 'blabla';
function getUserData() {
var deferred = $q.defer();
getUser(user).then(
function(response) {
getUserPreferences(response.user).then(
function(preferences) {
console.log('preferences', preferences);
},
function() {
deferred.reject();
}
);
getUserFavourites(response.user).then(
function(favourites) {
deferred.resolve();
console.log('favourites', favourites);
},
function() {
deferred.reject();
}
);
},
function() {
deferred.reject();
}
);
return deferred.promise;
}
getUserData().then(
function() {
console.log('got all user data');
}
);
A way to fix that is to use the async/await to make the code look synchronous.
var user = 'blabla';
async function getUserData() {
try {
var deferred = $q.defer();
let userInfo = await getUser(user)
let userPrefs = await getUserPreferences(userInfo.user)
console.log('preferences', userPrefs);
let userFavourites = await getUserFavourites(userInfo.user)
deferred.resolve();
console.log('favourites', userFavourites);
return deferred.promise;
} catch (error) {
deferred.reject();
}
}
getUserData().then(
function() {
console.log('got all user data');
}
);
Use $q.all to return a composite promise:
function getUserData() {
return getUser(user).then(function(response) {
var preferencesPromise = getUserPreferences(response.user);
var favouritesPromise = getUserFavourites(response.user);
return $q.all([preferencesPromise, favouritesPromise]);
});
}
Then extract the data from the composite promise:
getUserData().then([preferences, favourites] => {
console.log('got all user data');
console.log(preferences, favourites);
}).catch(function(error) {
console.log(error);
});
The $q.all method returns a single promise that will be resolved with an array/hash of values, each value corresponding to the promise at the same index/key in the promises array/hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.
For more information, see
AngularJS $q Service API Reference - $q.all
You must RETURN the nested promise in order to have a chain.
The problem here is you have 2 nested promises so you will need to return a Promise.all (or $q.all in your case) taking an array of the 2 promises returned by getUserPreferences and getUserFavorites:
var user = 'blabla';
function getUserPreferences(){
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve({color: 'green'});
},500);
});
}
function getUserFavorites(){
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve([{id: 1, title: 'first favorite'}, {id: 2, title: 'second favorite'}]);
},500);
});
}
function getUser(){
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve(user);
},500);
});
}
function getUserData() {
return getUser().then(
function(user) {
console.log(user);
var prefPromise = getUserPreferences(user).then(
function(preferences) {
console.log('preferences', preferences);
return preferences;
},
function(error) {
console.log("Error getting preferences");
throw error;
}
);
var favPromise = getUserFavorites(user).then(
function(favourites) {
console.log('favourites', favourites);
return favourites;
},
function(error) {
console.log("Error getting favorites");
throw error;
}
);
return Promise.all([
prefPromise,
favPromise
]);
},
function(err) {
console.log("Error getting user");
throw err;
}
);
}
getUserData().then(
function(results) {
console.log(results);
}
);
Note that for demo purpose I am using es6 Promise instead of angular $q but the spirit is the same:
$q.defer() => new Promise()
$q.all => Promise.all
As the Promise pattern is great to simplify async code and make it look like synchronous code, you can simplify the upper example with something like:
var user = { name: 'blabla'};
function getUserPreferences(user){
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve({color: 'green'});
},500);
});
}
function getUserFavorites(user){
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve([{id: 1, title: 'first favorite'}, {id: 2, title: 'second favorite'}]);
},500);
});
}
function getUser(){
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve(user);
},500);
});
}
function getUserData() {
return getUser()
.then(user => { // user is resolved
// running parallel promises to get user infos:
return Promise.all([
user,
getUserPreferences(user),
getUserFavorites(user)
]);
})
.then(results => {
// wrapping the results into something more semantic:
let userData = results[0];
userData.prefs = results[1];
userData.favs = results[2];
return userData;
});
}
getUserData().then(
function(userData) {
console.log('Final result:');
console.log(userData);
}
);
I'm trying to figure the concept of promises in javascript.
I've looked at this code:
new Promise(function(res,rej) {
res("aaa");
})
.then(function(result) {
console.log(result);
return "bbb";
})
.then(function(result) {
console.log(result);
return "ccc";
})
.then(function(result) {
console.log(result);
});
it prints:
aaa
bbb
ccc
to the console log.
a few questions:
is the then() method 1'st parameter is a function that will be run as the resolve() method?
is the then() method also returns a value which is a promise and this promise is the same promise as the one it is linked to (its parent) only the value of its resolve() method is the value return from the resolve() method inside the then()?
is this promise:
var myPromise = new Promise(function(res,rej) {
res("aaa");
})
.then(function(result) {
console.log(result);
return "bbb";
})
is equivalents to this promise below?
var myPromise = new Promise(function(res,rej) {
res("bbb");
})
in addition, what is happening when the then() accepts a promise?
like in this example?
var firstMethod = function() {
var promise = new Promise(function(resolve, reject){
setTimeout(function() {
console.log('first method completed');
resolve({data: '123'});
}, 2000);
});
return promise;
};
var secondMethod = function(someStuff) {
var promise = new Promise(function(resolve, reject){
setTimeout(function() {
console.log('second method completed');
resolve({newData: someStuff.data + ' some more data'});
}, 2000);
});
return promise;
};
var thirdMethod = function(someStuff) {
var promise = new Promise(function(resolve, reject){
setTimeout(function() {
console.log('third method completed');
resolve({result: someStuff.newData});
}, 3000);
});
return promise;
};
firstMethod()
.then(secondMethod)
.then(thirdMethod);
The then method's first param is the resolve function and the second param is the rejection function.
var resolvedPromise = new Promise(function(res,rej){ res({data: 7}) });
var rejectedPromise = new Promise(function(res,rej){ rej('error!!!!') });
resolvedPromise.then(function(res){ console.log('resolved:' + JSON.stringify(res)); }, function (err){ console.log('rejected:' + err); });
rejectedPromise.then(function(res){ console.log('resolved:' + JSON.stringify(res)); }, function (err){ console.log('rejected:' + err); });
The then method returns a promise based on the return type of the previous promise.
var promise = new Promise(function(res,rej){ res({data: 7}) });
promise.
then(function(res){ console.log(res); return res.data; }).
then(function(res){ console.log(res); return res + 1; }).
then(function(res){ console.log(res);});
No. The first code will log "aaa" and return a promise with "bbb" while the second will return "bbb" without logging "aaa";
The following function tries to return a promise that will only resolve when all the async HTTP calls have finished:
$scope.saveThat = function () {
var promises = [];
for (var i = 0; i < array.length; i++) {
var newPromise = $q.defer();
promises.push(newPromise);
// some more code...
(function (i, newPromise) {
$http(httpData)
.success(function (response) {
newPromise.resolve('Success');
})
.error(function (response) {
newPromise.reject('Error');
});
})(i, newPromise);
}
return $q.all(promises);
};
And the following snippet calls this function.
// save this...
var promise1 = $rootScope.saveThis(result.Person);
promise1.then(function (success) {
}, function (error) {
saveErrorMessage += 'Error saving this: ' + error + '.';
});
// save that...
var promise2 = $rootScope.saveThat(result.Person);
promise3.then(function (success) {
}, function (error) {
saveErrorMessage += 'Error saving that: ' + error + '.';
});
// wait until all promises resolve
$q.all([promise1, promise2])
.then(
function (success) {
$scope.$emit(alertEvent.alert, { messages: 'Saved successfully!', alertType: alertEvent.type.success, close: true });
}, function (error) {
$scope.$emit(alertEvent.alert, { messages: saveErrorMessage, alertType: alertEvent.type.danger });
});
The problem I have is that the second promise ($q.all([promise1, promise2])) resolves even when the promises in promise2 haven't resolved yet.
Because you are not creating an array of promise, Actually it contains a $q.defer() object. You should be using
promises.push(newPromise.promise);
instead of
promises.push(newPromise);
Also you need to avoid those anti-pattern, because you are creating $q object unnecessarily as you have promise object there which returned from the $http.get.
Code
$scope.saveThat = function() {
var promises = [];
for (var i = 0; i < array.length; i++) {
// some more code...
var promise = $http(httpData)
.then(function(response) {
return 'Success'; //returning data from success resolves that promise with data
}, function(response) {
return 'Error'; //returning data from error reject that promise with data
});
promises.push(promise); //creating promise array
}
return $q.all(promises); //apply $q.all on promise array
};
I've following two async operations and then final onResult and onFault defined. How can I chain following two async operations getConnection and then select and then finally calling onResult or onFault
Edit need help in promisifying following sequence.
new Promise(this.getConnection)
.then(this.select)
.then(this.onResult)
getConnection: function(resolve, reject) {
console.log('get connection')
if(database = common.model.connections.Sync.getConnection()) {
database.transaction(function(transaction){
resolve(transaction);
});
} else {
reject("Connection Error");
}
},
select: function(transaction) {
console.log('select', transaction)
var self = this;
var query = "SELECT * FROM configuration WHERE key = 'schedule'";
self.transaction.executeSql(query, [], function(transaction, resultSet){self.selectTransactionComplete(transaction, resultSet)}, function(){self.selectTransactionError()});
},
selectTransactionComplete: function(transaction, resultSet) {
console.log('select transaction complete')
if(resultSet.rows.length == 0) {
this.onResult(false);
} else if(new Date().getTime() - new Date(common.Config.getLastSync()).getTime() > resultSet.rows.item(0).value) {
this.onResult(true);
}
},
selectTransactionError: function(error) {console.log(this.constructor.NAME, this.selectSQL); console.log(error);},
onResult: function(data) {
console.log(data);
},
onFault: function(info) {
console.log(info)
}
after trying couple of things, is this how it's supposed to be done? please review
this.getConnection()
.then(this.select, this.getConnectionError)
.then(this.selectTransactionComplete, this.selectTransactionError)
getConnection: function() {
console.log('get connection')
var promise = new Promise(function(resolve, reject){
try {
database = common.model.connections.Sync.getConnection();
database.transaction(function(transaction){
resolve(transaction);
});
} catch(error) {
reject("Connection Error");
}
});
return promise;
},
getConnectionError: function(info) {
console.log("connectionError");
this.onFault();
},
select: function(transaction) {
console.log('select')
var self = this;
var promise = new Promise(function(resolve, reject){
var query = "SELECT * FROM configuration WHERE key = 'schedule'";
transaction.executeSql(query, [], function(transaction, resultSet){resolve(resultSet)}, function(error){reject(error)});
});
return promise;
},
selectTransactionComplete: function(resultSet) {
console.log('selectTransactionComplete')
/*if(resultSet.rows.length == 0) {
this.onResult(false);
} else if(new Date().getTime() - new Date(common.Config.getLastSync()).getTime() > resultSet.rows.item(0).value) {
this.onResult(true);
}*/
},
selectTransactionError: function(error) {
console.log('selectTransactionError')
//console.log(this.constructor.NAME, this.selectSQL);
//console.log(error);
},
onResult: function(data) {
console.log('onResult')
},
onFault: function(info) {
console.log('onFault')
}
If you are using a promise library like q, the way you would go about chaining the promises is as below;
getConnection: function() {
var deferred = Q.Defer();
console.log('get connection')
try {
database = common.model.connections.Sync.getConnection();
database.transaction(function(transaction){
deferred.resolve(transaction);
});
} catch(error) {
deferred.reject("Connection Error");
}
return deferred.promise;
}
when you call you would do something like below;
Q.when(getConnection)
.then(function(result){
// handle success or resolve
}, function(error){
// handle rejection.
};
Also I suggest reading the common js promises specification
I have written a function which opens an indexedDB. I'm using a promise, because I think it is the cleanest way to handle this.
this.$get = function($q, $rootScope, $window) {
return {
finalize: function() {
var deferred = $q.defer();
var dbRequest = $window.indexedDB.open(dbName, dbVersion);
dbRequest.onupgradeneeded = function(event) {
// XXX do something here.
};
dbRequest.onsuccess = function(event) {
db = event.target.result;
console.log('Success!');
deferred.resolve(event);
};
dbRequest.onerror = deferred.reject;
return deferred.promise;
}
};
};
In order to test this, I have created the following test function:
describe('finalize', function() {
it('should initialize the database', function(done) {
var promise = resource.finalize();
promise.then(function() {
console.log('resolved');
var transaction = resource.db.transaction(['Component']);
expect(transaction).toBeDefined();
done();
});
$rootScope.$apply();
});
});
This prints 'Success!' in the console, but the promise is never resolved.
If I move $rootScope.$apply() to the end of the onsuccess function, the promise is resolved, but only for one test. Other tests then throw an error Error: [$rootScope:inprog] $digest already in progress.
How should I resolve this promise? Is a callback function better?
this.$get = function($q, $rootScope, $window) {
return {
finalize: function() {
var deferred = $q.defer();
var dbRequest = $window.indexedDB.open(dbName, dbVersion);
dbRequest.onupgradeneeded = function(event) {
// XXX do something here.
};
dbRequest.onsuccess = function(event) {
db = event.target.result;
console.log('Success!');
$rootScope.$apply(function () {
deferred.resolve(event);
});
};
dbRequest.onerror = function (error) {
$rootScope.$apply(function () {
deferred.reject(error);
});
}
return deferred.promise;
}
};
};
Also you should use var promise = resource.finalize(); one time for all tests