I'm using Parse.com. There are many drills inside each package and many packages in each category.
I'm stuck on .then(function(result, result2, result3) on the last line. The promise can have multiple promises and it is variable. Is there a way to get write something like:
.then(function(multipleResults);
console.log(mulitpleResults);
Thanks!
var Parse = require('parse/node').Parse;
var _ = require('underscore');
var TrainingPackage = Parse.Object.extend("TrainingPackage");
var TrainingCategory = Parse.Object.extend("TrainingCategory");
var query2 = new Parse.Query(TrainingPackage);
var query = new Parse.Query(TrainingCategory);
query.equalTo("objectId", "kfHnYdd3T1");
query.find().then(function(cat){
query2.equalTo("category_id", cat[0]);
return query2.find();
}).then(function(results){
var promises = [];
_.each(results, function(result) {
var Drill = Parse.Object.extend("Drill");
var query3 = new Parse.Query(Drill);
query3.equalTo("package_id", result);
promises.push(query3.find());
});
return Parse.Promise.when(promises);
}).then(function(result, result2, result3){
console.log(result);
console.log(result2);
console.log(result3);
});
Due to the inherent nature of a promise it may only resolve to a single value. This being said what you probably want to do is take a look at the Promise.all API. Promise.all will create a new Promise from an array of promises and it will resolve when all your promises are resolved. E.g.
Promise.all([promise1, promise2]).then(function(results) {
// Both promise1 and promise 2 resolved and available at variable results
})
Working example from Mozilla Promise API:
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "foo");
});
Promise.all([p1, p2, p3]).then(function(values) {
console.log(values); // [3, 1337, "foo"]
});
Related
I am trying to write a series of AJAX requests into a dictionary.
I am attempting to use promises for this, however I am either writing the promise syntax incorrectly, or what I think may be happening is that the function is actually completing (for loop is done, and AJAX requests sent) but the AJAX requests are still not returned. Therefore this is still returning an empty dictionary.
let dict = {};
let activeMachines = ["41", "42", "43"];
let dataPromise = new Promise (function (resolve, reject)
{
for (let i = 0; i < activeMachines.length; i++)
{
let machineID = activeMachines[i]
let getAPIData = new XMLHttpRequest();
let url = 'http://127.0.0.1:8000/processes/apidata/' +machineID + '/';
getAPIData.open('GET', url);
getAPIData.send();
getAPIData.onload = function()
{
let APIData = JSON.parse(getAPIData.responseText);
dict['machine_' + machineID] = APIData[0].author_id;
dict['temp' + machineID] = APIData[0].tempData; //get value
dict['humid' + machineID] = APIData[0].humidData;
timeValue = String((APIData[0].dateTime));
dict['time' + machineID] = new Date(timeValue);
console.log("done");
}
}
resolve();
});
dataPromise.then(function() {console.log(dict);});
Is there a way to "sense" when all of the XMLHTTPRequests have returned?
#Rafael's answer will work, but it doesn't illuminate much about what's going wrong, since you're trying to grok the concept of Promises and write one yourself.
Fundamentally I think your approach has two missteps: 1. creating a single Promise that handles calls to all of your arbitrary list of "activeMachines", and 2. putting your resolve() call in the wrong place.
Usually a Promise looks like this:
const myPromise = new Promise(function(resolve, reject) {
doSomeAsyncWork(function(result) {
// Some kind of async call with a callback function or somesuch...
resolve(result);
});
}).then(data => {
// Do something with the final result
console.log(data);
});
You can simulate some kind of arbitrary asynchronous work with setTimeout():
const myPromise = new Promise(function(resolve, reject) {
// Resolve with "Done!" after 5 seconds
setTimeout(() => {
resolve("Done!");
}, 5000);
}).then(data => {
console.log(data); // "Done!"
});
However your original code puts the resolve() call in a weird place, and doesn't even pass it any data. It looks sorta equivalent to this:
const myPromise = new Promise(function(resolve, reject) {
// Resolve with "Done!" after 5 seconds
setTimeout(() => {
// Doing some work here instead of resolving...
}, 5000);
resolve();
}).then(data => {
console.log(data); // This would be "undefined"
});
Where you're doing a console.log("done"); in your original code is actually where you should be doing a resolve(someData);!
You're also trying to do side effect work inside of your Promise's async function stuff, which is really weird and contrary to how a Promise is supposed to work. The promise is supposed to go off and do its async work, and then resolve with the resulting data -- literally with the .then() chain.
Also, instead of doing multiple asynchronous calls inside of your Promise, you should generalize it so it is reusable and encapsulates only a single network request. That way you can fire off multiple asynchronous Promises, wait for them all to resolve, and then do something.
const activeMachines = ["41", "42", "43"];
// Make a reusable function that returns a single Promise
function fetchAPI(num) {
return new Promise(function(resolve, reject) {
const getAPIData = new XMLHttpRequest();
const url = "http://127.0.0.1:8000/processes/apidata/" + num + "/";
getAPIData.open("GET", url);
getAPIData.send();
getAPIData.onload = function() {
const APIData = JSON.parse(getAPIData.responseText);
const resolveData = {};
resolveData["machine_" + num] = APIData[0].author_id;
resolveData["temp" + num] = APIData[0].tempData; //get value
resolveData["humid" + num] = APIData[0].humidData;
timeValue = String(APIData[0].dateTime);
resolveData["time" + num] = new Date(timeValue);
resolve(resolveData);
};
});
}
// Promise.all() will resolve once all Promises in its array have also resolved
Promise.all(
activeMachines.map(ea => {
return fetchAPI(ea);
})
).then(data => {
// All of your network Promises have completed!
// The value of "data" here will be an array of all your network results
});
The fetch() API is great and you should learn to use that also -- but only once you understand the theory and practice behind how Promises actually operate. :)
Here's an example of the Fetch API which uses Promises by default:
let m_ids = [1,2,3,4];
let forks = m_ids.map(m => fetch(`http://127.0.0.1:8000/processes/apidata/${m}`));
let joined = Promise.all(forks);
joined
.then(files => console.log('all done', files))
.catch(error => console.error(error));
I hope this helps!
In the code below, I am trying to do the following:
Have Stats(), getOverallStats() and GetGroups() to run in parallel. Each returns a promise.
The forEach in GetGroups.then() should run sequentially to ensure the output is in the correct order.
Once ALL of the above is complete, then run some more code.
However, I am getting very confused with the promises! The logging gives me:
looping
here
looping
looping
But what I am looking for is here to be at the end.
Finally, at the moment I have hardcoded loopAgelist[1] for testing purposes. But, I actually want to be able to loop through loopAgelist[] with a timeout in between! I would appreciate if someone could explain some promise 'rules' to use in these complicated cases.
var loopAgeList;
var looppromises = [];
getAgeGroupList().then(function (loopAgeList) {
var statsPromise = Stats(loopAgeList[1]);
var oStatsPromise = getOverallStats();
var grpPromise = GetGroups(loopAgeList[1]).then(function (groups) {
var promise = Parse.Promise.as();
groups.forEach(function (grp) {
promise = promise.then(function () { // Need this so that the tables are drawn in the correct order (force to be in series)
console.log("looping")
if (grp != "KO"){
var standingsPromise = Standings(loopAgeList[1], grp);
looppromises.push(standingsPromise);
}
var fixPromise = GetFixtures(loopAgeList[1], grp);
looppromises.push(fixPromise);
return fixPromise;
});
});
return Parse.Promise.all(looppromises);
});
var promises = [statsPromise, oStatsPromise, grpPromise, looppromises];
Parse.Promise.all(promises).then(function(results) {
console.log("here");
});
});
The rewrite can be improved significantly by adopting a couple simple style rules: (1) there's no need to create a resolved promise and then chain to it (in fact, most would consider this an anti-pattern), (2) promises to be run together by iterating an array of operands is the perfect application of array .map (not reduce), (3) most importantly, smaller, testable, promise-returning functions always clears up the mystery.
Putting all that together, the main function can be as simple as this...
function loopOverOnce(agegroup) {
let statsPromise = Stats(agegroup);
let oStatsPromise = getOverallStats();
let grpPromise = GetGroups(agegroup).then(function(groups) {
return getStandingsAndFixturesForGroups(groups, agegroup);
});
return Parse.Promise.all([statsPromise, oStatsPromise, grpPromise]);
}
Let's write getStandingsAndFixturesForGroups. It's only job will be map the groups and aggregate promises to do work on each...
function getStandingsAndFixturesForGroups(groups, agegroup) {
let promises = groups.map(function(group) {
return getStandingsAndFixturesForGroup(group, agegroup);
});
return Parse.Promise.all(promises);
}
Now, getStandingsAndFixturesForGroup, a function to do the async work on a single group, conditionally for part of the work...
function getStandingsAndFixturesForGroup(group, agegroup) {
let promises = (group != "KO")? [ Standings(agegroup, grp) ] : [];
promises.push(GetFixtures(agegroup, group));
return Parse.Promise.all(promises); // this is your standings promise (conditionally) and fixtures promise
}
Done. I'd test this code in the reverse order that it's presented here.
EDIT The OP also asks how to perform several promises, serially, interspersed with timeouts. Here's my advice.
First, a slightly simpler version of your delay function, which is a good example when it is right to create a new promise (because there's nothing at bottom to call to get one)
function delay(interval) {
return new Promise(function(resolve, reject){
setTimeout(function() {resolve();}, interval);
});
};
And reducing is a good way to build a list of promises, including interspersed delays...
getAgeGroupList().then(function (loopAgeList) {
loopAgeList.reduce(function(promise, agegroup) {
return promise.then(function() {
let promises = [loopOverOnce(agegroup), delay(15000)];
return Promise.all(promises);
});
}, Promise.as());
});
A couple notes: this results in a sequence like loopOverOnce, timeout, loopOverOnce, timeout, ... etc.. If you'd like a timeout first, reverse the order of the little chain in the inner loop:
[ delay(15000), loopOverOnce(agegroup) ]
Final note is that all this could be made even shorter and prettier by adopting ES6 fat arrow syntax for anonymous functions, e.g.
loopAgeList.reduce((promise, agegroup) => {
promise.then(() => Promise.all([loopOverOnce(agegroup), delay(15000)]));
}, Promise.as());
I have re-written it using reduce and seem to have it working. Comments on this would be welcome (i.e. are there any issues with this code).
function loopOverOnce(agegroup) {
var statsPromise = Stats(agegroup);
var oStatsPromise = getOverallStats();
var grpPromise = GetGroups(agegroup).then(function (groups) {
function getStandingsAndFixtures(groups) {
var promise = Parse.Promise.as();
return groups.reduce(function (promise, grp) {
return promise.then(function (result) {
var standingsPromise = Parse.Promise.as();
if (grp != "KO") {
standingsPromise = Standings(agegroup, grp);
}
var fixPromise = GetFixtures(agegroup, grp);
console.log("fixPromise");
return Parse.Promise.all([standingsPromise, fixPromise]);
});
}, promise);
}
var sfPromise = getStandingsAndFixtures(groups).then(function () { console.log("Test1") });
return sfPromise;
});
return Parse.Promise.all([statsPromise, oStatsPromise, grpPromise]).then(function () { console.log("Test2") });
}
getAgeGroupList().then(function (loopAgeList) {
// https://stackoverflow.com/questions/39538473/using-settimeout-on-promise-chain
function delay(t, v) {
return new Promise(function (resolve) {
setTimeout(resolve.bind(null, v), t)
});
}
var promise = Parse.Promise.as();
loopAgeList.reduce(function (promise, agegroup) {
return promise.then(function () {
return delay(15000).then(function () {
return loopOverOnce(agegroup);
});
});
}, promise);
});
The problem is, that you pass a nested array to Promise.all:
var promises = [statsPromise, oStatsPromise, grpPromise, looppromises];
Just flatten that:
var promises = [statsPromise, oStatsPromise, grpPromise, ...looppromises];
// Or
var promises = [statsPromise, oStatsPromise, grpPromise].concat(looppromises);
However you still need to await promise somewhere to ensure that the chain finished its execution, otherwise looppromise will be empty.
All in all it might be better to use async / await to make everything much more readable:
(async function() {
const ageGroups = await getAgeGroupList();
const statsPromise = Stats(ageGroups[1]);
const overallStatsPromise = getOverallStats();
const groups = await GetGroups(ageGroups[1]);
for(const group of groups) {
const [standings, fixtures] = await Promise.all(
Standings(ageGroups[1], group),
GetFixtures(ageGroups[1], group)
);
// Do something with standings & fixtures
}
const [stats, overallStats] = await Promise.all(statsPromise, overallStatsPromise);
// Do whatever you want with stats etc.
})();
I am using Dojo 1.10 by the way which came with Deferred and Promise API. So the scenario is as below:
var deferred_1 = new Deferred();
deferred_1 .then(function(value){
// do something
return something;
})
var deferred_2 = new Deferred();
deferred_2.then(function(value){
// do something
return something;
})
var completeFunc = function(value){
console.log("done");
}
//run completeFunc after completion of both deferred_1.then and
deferred_2.then
How do I ensure the completion of both deferred objects callback functions in deferred_1.then and deferred_2.then are completed before running another function.
Use Promise.all to create a Promise that resolves only after all promises you pass into it are resolved. Note that you have to save a reference to the new promises:
const deferred_1 = new Deferred();
const d1 = deferred_1.then(function(value) {
// do something
return something;
});
const deferred_2 = new Deferred();
const d2 = deferred_2.then(function(value) {
// do something
return something;
})
const completeFunc = function(value) {
console.log("done");
}
Promise.all([d1, d2]).then(completeFunc);
You can use dojo/promise/all function.
Example from documentation:
require(["dojo/promise/all"], function(all){
all([promise1, promise2]).then(function(results){
// results will be an Array
});
// -- or --
all({
promise1: promise1,
promise2: promise2
}).then(function(results){
// results will be an Object using the keys "promise1" and "promise2"
});
});
Since the ES6 Promise does not have a deferred object confused on how to go about converting this to work with ES6 Promises. One solution that I was looking at is to add a deffered object manually to the Promise Constructor. see the No deffered section for the example code
Reason: I am converting the angularjs app to angular4 and using it to better understand how to work with ES6 Promises. I was going to add the deferred object, but thought that was too much of a workaround.
function generateImages() {
console.log('generateImagesCalled')
// set up promises
var fullDeferred = $q.defer();
var thumbDeferred = $q.defer();
var resolveFullBlob = blob => fullDeferred.resolve(blob);
var resolveThumbBlob = blob => thumbDeferred.resolve(blob);
var displayPicture = (url) => {
var image = new Image();
image.src = url;
// Generate thumb
var maxThumbDimension = THUMB_IMAGE_SPECS.maxDimension;
var thumbCanvas = _getScaledCanvas(image, maxThumbDimension);
thumbCanvas.toBlob(resolveThumbBlob, 'image/jpeg', THUMB_IMAGE_SPECS.quality);
// Generate full
var maxFullDimension = FULL_IMAGE_SPECS.maxDimension;
var fullCanvas = _getScaledCanvas(image, maxFullDimension);
fullCanvas.toBlob(resolveFullBlob, 'image/jpeg', FULL_IMAGE_SPECS.quality);
}
var reader = new FileReader();
reader.onload = (e) => {
displayPicture(e.target.result);
}
reader.readAsDataURL(vm.currentFile);
return $q.all([fullDeferred.promise, thumbDeferred.promise]).then(results => {
console.log(results);
return {
full: results[0],
thumb: results[1]
}
});
}
In some special cases resolve and reject can be exposed in order to replicate deferreds:
let pResolve;
let pReject;
const p = new Promise((resolve, reject) => {
pResolve = resolve;
pReject = reject;
});
Or deferred object can be formed:
const deferred = {};
deferred.promise = new Promise((resolve, reject) => {
Object.assign(deferred, { resolve, reject });
});
Since deferreds aren't Promise built-in feature and are prone to be antipattern, it's preferable to solve this with constructor function only, when it is applicable.
In the code above displayPicture and the fact that promises cannot be formed instantly but on load event limits the ways the situation can be efficiently handled. It can be improved with sticking to promises for events (also beneficial for further conversion to async..await):
const readerPromise = new Promise(resolve => reader.onload = resolve);
reader.readAsDataURL(vm.currentFile);
const blobPromises = readerPromise.then(e => {
const url = e.target.result;
const fullBlobPromise = new Promise(resolve => {
...
fullCanvas.toBlob(resolve, ...);
});
const thumbBlobPromise = ...;
return Promise.all([fullBlobPromise, thumbBlobPromise]);
});
The promises should also make use of Angular digests in order to provide same control flow. Since promise internals don't rely on Angular services, there can be a single digest at the end:
return blobPromises.then(
() => $rootScope.$apply(),
err => {
$rootScope.$apply();
throw err;
}
);
// or
return $q.resolve(blobPromises);
in ES6 it would be
let resolve1 = new Promise<string>((resolve, reject) => {
if (error) {
reject();
} else {
resolve();
}
});
Do the same for the number of promise you want to resolve, then if you want to run multiple promises, use :
Promise.all([resolve1, resolve2]).then(values => {
console.log(values);
});
Or you can also declare your promises array and then resolve it
let resolvedPromisesArray = [Promise.resolve(val1), Promise.resolve(val2)];
let p = Promise.all(resolvedPromisesArray);
It will fail if one of the promises is rejected, and will run then if all success ;)
https://codecraft.tv/courses/angular/es6-typescript/promises/
I wrote a sequence of functions which include Parse queries:
var query1 = new Parse.Query(Events);
query1.equalTo("id_organizer", $localStorage.username);
query1.greaterThanOrEqualTo("date_start",currentTime)
query1.each(function(results){
var object = results;
eventname = object.get("event_name");
datestart = object.get("date_start");
location = object.get("location");
id_event = object.get("id_event")
eventimagefile = object.get("event_image");
eventimageurl = eventimagefile.url();
description = object.get("description");
id_organizer = object.get("id_organizer");
min_twitter_followers = object.get("min_twitter_followers");
min_instagram_followers = object.get("min_instagram_followers");
min_facebook_friends = object.get("min_facebook_friends");
max_number_requests = object.get("max_number_requests");
eventDetails.push({'name':eventname,'location':location, 'datestart':datestart, 'eventphoto':eventimageurl,'organizer':id_organizer, 'description':description, 'minTwitterFollowers':min_twitter_followers, 'minFacebookFriends':min_facebook_friends, 'minInstagramFollowers':min_instagram_followers,'maxNumberRequests':max_number_requests, 'id_event':id_event})
}).then(function(){
alert("start")
var Attendees = Parse.Object.extend("Attendees");
eventDetails.forEach(function(e){
var query2 = new Parse.Query(Attendees);
query2.equalTo("event_id", e.id_event);
query2.count({
success: function(number) {
e["n_requests_received"] = number;
alert("received")
}
})
var query3 = new Parse.Query(Attendees);
query3.equalTo("event_id", e.id_event);
query3.equalTo("status", "confirmed")
query3.count({
success: function(number) {
e["n_requests_confirmed"] = number;
// alert("confirmed")
}
})
})
}).then(function(){
alert("end")
alert(JSON.stringify(eventDetails))
$scope.events = eventDetails;
$localStorage.events = eventDetails;
});
})
unfortunately the alert "end" is printed right after "start" without waiting for the queries (query1,query2) inside the foreach loop to be executed. Do you know how I can make the for loop with the 2 queries executed before the next then?
.then(function(){
alert("start")
var Attendees = Parse.Object.extend("Attendees");
return Promise.all(eventDetails.map(detail => {
return Promise.all([new Promise((res, rej) => {
var query2 = new Parse.Query(Attendees);
query2.equalTo("event_id", detail .id_event);
query2.count({
success: function(number) {
detail["n_requests_received"] = number;
alert("received")
res('Some value if required'); //These are needed or promise chain will hang
}
});
}),
new Promise((res, rej) => {
var query3 = new Parse.Query(Attendees);
query3.equalTo("event_id", detail .id_event);
query3.equalTo("status", "confirmed")
query3.count({
success: function(number) {
detail["n_requests_confirmed"] = number;
// alert("confirmed")
res('Some value if required'); //These are needed or promise chain will hang
}
});
})]);
}));
})
Promise chains will wait for promises. That is if a .then anywhere in the chain receives an unresolved promise it will wait for that promise to resolve.
So what you could do is use Promise.all which expects an array of promises and will resolve when they do. So map over your array eventDetails creating a promise from each cell. Inside each cell use another Promise.all to create two new promises for both of your Query objects.
This way your .then will wait for your eventDetails array of promises to resolve. Then each cell in eventDetails will wait for your array of queries to resolve.
NOTE: The new Promise() where we are creating promises. It passes you two callbacks to resolve or reject the promise. This is how you wrap old callback style API's in a promise. However if you do not call either of them to resolve or reject then the whole promise chain could hang (depending on its design) waiting for that promise to resolve.
Hope that makes sense. This is pseudo code but you should get the idea.
Disclaimer i have not used Parse.Query i do not know what it is. However this method is how i would handle the above program flow of using an old callback style API, where you essentially have nested loops.
Extra Disclaimer: My example is using ES6 syntax. Not all browsers (IE) support that yet.
EDIT: The op asked for a version without using the ES6 arrow functions so here you are. There's lots of info on StackOverflow about arrow functions, that and Google. As you can see theres not much difference in terms of syntax. The power comes from how it binds the scope and also when your writing code in a more functional style.
.then(function(){
alert("start")
var Attendees = Parse.Object.extend("Attendees");
return Promise.all(eventDetails.map(function (detail) {
return Promise.all([new Promise(function (res, rej) {
var query2 = new Parse.Query(Attendees);
query2.equalTo("event_id", detail .id_event);
query2.count({
success: function(number) {
detail["n_requests_received"] = number;
alert("received")
res('Some value if required'); //These are needed or promise chain will hang
}
});
}),
new Promise(function (res, rej) {
var query3 = new Parse.Query(Attendees);
query3.equalTo("event_id", detail .id_event);
query3.equalTo("status", "confirmed")
query3.count({
success: function(number) {
detail["n_requests_confirmed"] = number;
// alert("confirmed")
res('Some value if required'); //These are needed or promise chain will hang
}
});
})]);
}));
})