I have this code:
child.get('authors').forEach(author => author.get('books').forEach(book => {
let time = (Math.floor(Math.random() * 10) + 1) * 1000;
Ember.run.later(() => {
//Some operations and then every book in the forEach is added to my favoriteList
favoriteList.pushObject(book);
}, time);
}));
.then(() => {
console.log('Yoo! Finally!')
})
I need that message (and other actions) to execute finally, after all forEach. But now that message is consolelogged before that forEach get finished. Why?
I know I have to use: RSVP, Promises, EmberPromiseMixin and these stuff but itās a mess!
Would you suggest the code and something beautiful site or book or something to finally learn once for all this stuff?
Hi @johnunclesam, Promises can definitely be a little confusing to start off with, but once you get used to the basic syntax and ideas they are pretty awesome and very powerful. Iāll try and break down a few of the basic ideas and give you a little code. Unfortunately Iām not aware of any really great resources for learning them, you may just have to read a lot of code and google around. Promises arenāt really an Ember concept, Ember just happens to use them pretty heavily. Ember uses RSVP.js for promises so I would start there. I will say I find just the README for RSVP.js a pretty good primer. Your mileage may vary.
So for starters, an Ember.run.later has nothing to do with promises. It doesnāt return a promise or anything, it just waits a while to run the function. Same with forEach, no promises involved by default. However you can write a promise that will resolve when the run.later happens, thus giving you the behavior that you desire. Basically a promise is just function that executes and then either resolves on success or rejects on error. This can be anything you want. The most common use case is for AJAX requests. In your case though you can write a promise that resolves when your run.later happens and all of the operations in it successfully complete.
So I think what youāre looking for is not just one promise, but a bunch of promises that all must be resolved before the .then function gets executed. RSVP has you covered as it provides an āallā method. So to break down what you want to do a little bit:
- first you want to loop over your authors and then your books, and do some operation on each one, and return a promise
- then you want to use RSVP.all to make a āsuper promiseā out of all your little promises.
- then you want to use
.then
on your āsuper promiseā to do something after ALL of your promises resolve
So your code might look something like this:
let promises = [];
child.get('authors').forEach(author => author.get('books').forEach(book => {
// this is how to create a promise
var promise = new RSVP.Promise(function(resolve, reject){
let time = (Math.floor(Math.random() * 10) + 1) * 1000;
Ember.run.later(() => {
//Some operations and then ...
favoriteList.pushObject(book);
// this is how to resolve the promise, you could pass a value or not
// if you wanted to resolve to the book for example, you could say resolve(book);
resolve();
}, time);
if(something went wrong) {
// this is how to reject the promise, you may not need to do this in your case
reject(error message);
}
});
promises.push(promise)
}));
RSVP.all(promises).then(function(/* arguments can be passed here if you have any */) {
// contains an array of results for the given promises, for example if you passed 'book' into resolve
// like suggested above you'd get an array of books here, or you could just not worry about results
// and just do some operations here like printing this:
console.log('Yoo! Finally!')
}).catch(function(error message){
// if any of the promises fails. This catch function is only necessary if you want to handle errors
});
I stumbled upon an interesting post about promises recently that shows how you might solve this a little differently than what @dknutsen shows above. Instead of firing off all and resolving each in parallel, the reduce below allows you to resolve one after another (with chaining). Not necessarily better than what you see above with RSVP.all(promises)
just a different look at the problem that I hope is useful in some way (now or in the future)
urls.reduce(
(p, url) =>
p.then(() => fetch(url).then(handleResponse)),
Promise.resolve());
1 Like
Basically with this method that you suggest I take advantage of the blocking attitude of forEach
, right?
I used āEmber.run.later
ā only to simulate an eventual server delay for all āget
sā.
So I do not need a new promise for every forEach
.
So I do not need the array promises []
.
Thanks for the suggestion though.
Sorry @dknutsen, if I use this modified version of tour code itās the same?
child.get('authors').forEach(author => author.get('books').forEach(book => {
favoriteList.pushObject(book);
}));
console.log('Yoo! Finally!')
What are the differences?
Except for the Ember.run.later()
which I use to simulate a server delay for all āgetsā.
There are differences?
@johnunclesam Iām not 100% sure what you mean when you say āserver delay for all āgetsāā but if you mean that the authors and books are nested async relationships via Ember data (so when you do child.get(āauthorsā) it fetches the authors from the server, and when you do author.get(ābooksā) it fetches the books from the server) then I donāt think that either your code or my code would do what you want.
Letās break down your most recent version (again this is all based on the assumption that authors and books are async relationships):
child.get('authors').forEach(author => author.get('books').forEach(book => {
favoriteList.pushObject(book);
}));
- First youāre saying
child.get('authors')
. Now, assuming child is some ember data model and āauthorsā is an async relationship on that model, then this call is going to return a promise which will be resolved when the data from the server has been fetched.
- However forEach just loops over whatever it is given, it doesnāt (to my knowledge least) have any idea what a promise is, so my guess is that itās NOT going to wait for the promise to resolve before executing, basically meaning it wonāt execute at all.
- your second
.get
will behave the same way, in that it will not wait for the server fetch before executing the second forEach.
So I think what you want is more like this:
child.get('authors').then(authors => {
authors.forEach(author => {
author.get('books').then(books => {
books.forEach(book => {
favoriteList.pushObject(book);
});
});
});
});
This will get all author relationships asynchronously, then loop over them and for each author get all related books asynchronously, then for each book it will push it into favoriteList. Now of course there is still the issue of wanting to know when all of this is done. So this is where you may want to employ something like RSVP.all or something more like @toranb posted above. Basically, each .get
that you do returns a promise so you want to collect those promises and then wait until all of them have resolve. And actually thereās probably a better way to write it anyway, letās try it:
child.get('authors').then(authors => {
var bookPromises = [];
authors.forEach(author => {
bookPromises.push(author.get('books'));
});
RSVP.all(bookPromises).then(booksLists => {
// bookLists is an array of author->books lists, so it's an array of arrays of books
bookLists.forEach(bookList => {
bookList.forEach(book => { favoriteList.pushObject(book); });
});
console.log('Yoo! Finally!')
});
});