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
};
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);
}
);
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>
I am struggling with the Firebase Queries, I have this Factory:
.factory("usuariosFac", ["$firebaseArray","$q","$firebaseObject",
function($firebaseArray,$q,$firebaseObject) {
return {
getByEmail: function(email){
var ref = firebase.database().ref("usuarios");
var query=ref.orderByChild("email").equalTo(email).on("child_added", function(data) {
console.log(data.val());
return data.val();
});
}
}
}
])
This function is in my Controller:
$scope.findUser = function() {
$scope.usuario=usuariosFac.traeGrupoPorEmail($scope.formLogin.usuario);
};
When I run it, the console Log inside the Factory prints fine. But $scope.usuario is Undefined, why is this?
But $scope.usuario is Undefined, why is this?
The callback function is being called asychronously. The return statement inside a nested function does not return values to the parent function.
Instead, create and return a promise:
app.factory("usuariosFac", ["$firebaseArray","$q","$firebaseObject",
function($firebaseArray,$q,$firebaseObject) {
return {
getByEmail: function(email){
//Create defer object
var future = $q.defer();
var ref = firebase.database().ref("usuarios");
var query=ref.orderByChild("email")
.equalTo(email)
.once("child_added",
function onSuccess(data) {
console.log(data.val());
//RESOLVE
future.resolve(data.val());
},
function onReject(error) {
//OR REJECT
future.reject(error);
}
);
//RETURN promise
return future.promise;
}
}
}
])
In the controller, use the .then method of the returned promise:
$scope.findUser = function() {
var promise = usuariosFac.traeGrupoPorEmail($scope.formLogin.usuario);
promise.then(function onSuccess(data) {
$scope.usuario = data;
}).catch(function onReject(error) {
console.log(error);
throw error;
});
};
I have the following piece of code right now:
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);
recordPerfMetrics: function(url) {
var self = this;
var perf, loadTime, domInteractive, firstPaint;
var perfData = {};
readFile('urls.txt', 'UTF-8').then(function (urls, err) {
if (err) {
return console.log(err);
}
var urls = urls.split("\n");
urls.shift();
urls.forEach(function(url) {
console.log(url);
self.getStats(url).then(function(data) {
data = data[0];
loadTime = (data.loadEventEnd - data.navigationStart)/1000 + ' sec';
firstPaint = data.firstPaint;
domInteractive = (data.domInteractive - data.navigationStart)/1000 + ' sec';
perfData = {
'URL' : url,
'firstPaint' : firstPaint,
'loadTime' : loadTime,
'domInteractive' : domInteractive
};
console.log(perfData);
}).catch(function(error) {
console.log(error);
});
});
// console.log(colors.magenta("Starting to record performance metrics for " + url));
// this.storePerfMetrics();
});
},
getStats: function(url) {
return new Promise(function(resolve, reject){
console.log("Getting data for url: ",url);
browserPerf(url, function(error, data) {
console.log("inside browserPerf", url);
if (!error) {
resolve(data);
} else {
reject(error);
}
}, {
selenium: 'http://localhost:4444/wd/hub',
browsers: ['chrome']
});
});
},
This is basically reading urls from a file and then calling a function browserPerf whose data being returned is in a callback function.
The console.log("Getting data for url: ",url); is in the same order as the urls that are stored in the file,
but the console.log("inside browserPerf", url); is not conjunction as the same and as expected.
I expect the order of the urls to be:
console.log(url);
console.log("Getting data for url: ",url);
console.log("inside browserPerf", url);
But for reason only the first two are executed in order but the third one is fired randomly after all are being read.
Any idea what i am doing wrong here?
Since you are using Bluebird, you can replace your .forEach() loop with Promise.mapSeries() and it will sequentially walk through your array waiting for each async operation to finish before doing the next one. The result will be a promise who's resolved value is an array of results. You also should stop declaring local variables in a higher scope when you have async operations involved. Declare them in the nearest scope practical which, in this case is the scope in which they are used.
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);
recordPerfMetrics: function() {
var self = this;
return readFile('urls.txt', 'UTF-8').then(function (urls) {
var urls = urls.split("\n");
urls.shift();
return Promise.mapSeries(urls, function(url) {
console.log(url);
return self.getStats(url).then(function(data) {
data = data[0];
let loadTime = (data.loadEventEnd - data.navigationStart)/1000 + ' sec';
let firstPaint = data.firstPaint;
let domInteractive = (data.domInteractive - data.navigationStart)/1000 + ' sec';
let perfData = {
'URL' : url,
'firstPaint' : firstPaint,
'loadTime' : loadTime,
'domInteractive' : domInteractive
};
console.log(perfData);
return perfData;
}).catch(function(error) {
console.log(error);
throw error; // keep the promise rejected
});
});
// console.log(colors.magenta("Starting to record performance metrics for " + url));
// this.storePerfMetrics();
});
},
getStats: function(url) {
return new Promise(function(resolve, reject){
console.log("Getting data for url: ",url);
browserPerf(url, function(error, data) {
console.log("inside browserPerf", url);
if (!error) {
resolve(data);
} else {
reject(error);
}
}, {
selenium: 'http://localhost:4444/wd/hub',
browsers: ['chrome']
});
});
},
You would use this like this:
obj.recordPerfMetrics().then(function(results) {
// process results array here (array of perfData objects)
}).catch(function(err) {
// error here
});
Summary of changes:
Return promise from recordPefMetrics so caller can get data
Use Promise.mapSeries() instead of .forEach() for sequential async operations.
Return promise from Promise.mapSeries() so it is chained with prior promise.
Move variable declarations into local scope so there is no change of different async operations stomping on shared variables.
Rethrow .catch() error after logging so the reject propagates
return perfData so it becomes the resolved value and is available in results array.
I'm trying to follow the 'when' example on Parse JavaScript SDK: Parse.Promise
with the following code:
GetFromDB2 = function(id) {
var DB2 = Parse.Object.extend("DB2");
var q = new Parse.Query(DB2);
q.get(id, {
success: function(res) {
return Parse.Promise.as(10);
},
error: function(res, err) {
console.log( err);
}
});
}
GetData = function() {
var DB1 = Parse.Object.extend("DB1");
var query = new Parse.Query(DB1);
query.equalTo("param", "close");
query.find().then(function(results) {
var promises = [];
_.each(results, function(res) {
promises.push(GetFromDB2(res.get("user")));
});
Parse.Promise.when(promises).then(function() {
console.log(arguments); // expect: [10, 10, ...]
})
});
};
The length of the array arguments is correct but not sure why its values are undefined.
As written, GetFromDB2() returns undefined. To deliver a value, it must return either a value or a promise.
At its simplest, to deliver 10, you could write :
function GetFromDB2(id) {
return 10;
}
But to be asynchronous, and still deliver 10, you need to return a promise that will resolve to 10 :
function GetFromDB2(id) {
return Parse.Promise.as(10);
}
Or the .get(id) query you really want :
function GetFromDB2(id) {
var DB2 = Parse.Object.extend('DB2');
var q = new Parse.Query(DB2);
return q.get(id).then(function(res) {
return 10;
});
//omit `.then(...)` entirely to deliver `res`
}
Now GetData() can be written as follows :
function GetData() {
var DB1 = Parse.Object.extend('DB1');
var query = new Parse.Query(DB1);
query.equalTo('param', 'close');
return query.find().then(function(results) {
var promises = _.map(results, function(res) {
return GetFromDB2(res.get('user'));
});
Parse.Promise.when(promises).then(function() {
console.log(arguments); // expect: [10, 10, ...]
}, function(err) {
console.log(err);
return err;
});
});
};
Notes:
promises = _.map(...) is more elegant than _.each(...) plus `promises.push(...).
moving the error handler into GetData() allows a greater range of possible errors to be handled, eg an error arising from query.find().
By returning a promise, GetData()'s caller is also informed of the eventual asynchronous outcome.