Turkhan Badalov Turkhan Badalov - 3 years ago 65
Javascript Question

Does promise-chaining guarantee that a callback of the next promise is called only after preceding one is finished?

I met an article about promises in js, where the author shows a piece of code:

// I want to remove() all docs
db.allDocs({include_docs: true}).then(function (result) {
result.rows.forEach(function (row) {
db.remove(row.doc);
});
}).then(function () {
// I naively believe all docs have been removed() now!
});


and says


What's the problem with this code? The problem is that the first
function is actually returning undefined, meaning that the second
function isn't waiting for db.remove() to be called on all the
documents. In fact, it isn't waiting on anything, and can execute when
any number of docs have been removed!


So, as I understood, if the second callback
function() {}
accepts no arguments, it actually doesn't wait for the end of the callback
function(result)
. Am I inferring correctly? Because when I run the following piece of code, it gives me an opposite result:

var array = [];
for ( let i = 0; i < 1000; i++ )
array.push(i);

var promise = new Promise((resolve, reject) => {
setTimeout(() => resolve(), 1000);
});

promise
.then(() => array.forEach(item => console.log(item)))
.then(() => console.log("invoked earlier"));


I wait for "invoked earlier" to appear in the middle of printed numbers. But doesn't matter how large the number of items is. "Invoked earlier" appears always at the end. Can someone explain me what I am missing? Maybe the article is outdated and something has changed since then?

Answer Source

In order to guarantee this, you actually have to return promises from the previous promise.

Whatever you return from a promise will be passed into the next promise.

In your case, you're returning undefined.

If, instead, you returned a promise, then the promise would be resolved, and the second promise would run after that happened, and would be passed whatever value the promise resolved with.

So yes, promises are guaranteed to run one after another, but if you choose to do something async inside of their callback, and don't bother chaining it back into the promise by returning it, then they're not going to bother to wait for anything (because they don't know that there's anything to wait for).

I'm assuming db.remove returns a promise...

...so, knowing that we have to return a promise from the callback, in order for it to actually wait for async stuff to happen, I would do something like this.

.then(result => Promise.all(result.rows.map(row => db.remove(row.doc))))
.then((listOfRemoves) => console.log(`removed`));

Whether the second function takes 1 argument or 0 arguments is 100% inconsequential as to when the second function runs.

Edit

examples:

.then(() => setTimeout(() => console.log('2nd', 20000)))
.then(() => console.log('first'));

This happens because the first then has NO idea that there is a setTimeout happening. It's not a mind-reader, it just runs the code and passes the return value along (in this case, undefined).

If the return value happens to be a promise, though, it will wait until that promise is done, get the value from it, and pass that on.

.then(() => new Promise(resolve => setTimeout(resolve, 20000)))
.then(() => console.log('I waited 20 seconds.'));

Because it's returning a promise, it will call the then of that promise, and wait to get the value, to pass on.

The reason your tests are failing is because you're basically doing this.

.then(() => console.log('first'))
.then(() => console.log('second'));

These are guaranteed to fire in this order. Period. All of the other ones have been firing in that order as well. It's just that they are also scheduling async processes, just like all other callbacks/timeouts that use async I/O in JS.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download