Tom Tanner
Tom Tanner

Reputation: 9354

How do promise chains start and finish

I'm a little confused about how the sequencing works in various documents I've come across. For instance, I have seen this sort of thing

let p = Promise.resolve();
for (let x in something)
{
    /* do some hairy time consuming code */
    p = p.then(dosomething(x));
}
return p.then(finalthing()).catch(pretendnobadthing());

What I really don't understand is that if you do something that takes a large amount of time in the main code, wouldn't that mean that one of the promises could complete before the main code actually got round to to setting up the .then.

Can you always use .then/.catch on a promise, and if its already reached completion, it'll carry on from the point it got to, so it conceptually looks like a big chain that will run to completion at some indeterminate point? And if so, doesn't that mean every time you create a promise chain it'll hang about for ever?

Upvotes: 0

Views: 1138

Answers (2)

jfriend00
jfriend00

Reputation: 707446

Can you always use .then/.catch on a promise, and if its already reached completion, it'll carry on from the point it got to, so it conceptually looks like a big chain that will run to completion at some indeterminate point?

Yes, you can always use .then/.catch on a promise. When you call p.then(), there are three possibilities.

  1. The promise p is still pending (not fulfilled or rejected yet). If that's the case, then the function reference(s) you passed to .then() are registered as listeners for that promise. So, when a future state transition happens on the promise (either going from pending => fulfilled or from pending => rejected), then the appropriate registered listeners will be called.

  2. The promise p is already fulfilled. If that's the case, then calling .then(f1, f2) will schedule f1 to be called on the next tick (after the current piece of Javascript finishes executing) and it will be passed the saved resolved value.

  3. The promise p is already rejected. If that's the case, then calling .then(f1, f2) will schedule f2 to be called on the next tick (after the current piece of Javascript finishes executing) and it will be passed the saved reject reason.

So, it's perfectly safe to call .then() on a promise that is already fulfilled or rejected. The appropriate listener will just be scheduled to run on the next tick.

The same logic applies to .catch() except it is only interested in cases 1 and 3 above.

Here are

And if so, doesn't that mean every time you create a promise chain it'll hang about for ever?

Promises are just objects like any other objects in Javascript. They will hang around only while other code still has some live reference to them. As soon as there is no longer any way to reach that promise object, they will be eligible for garbage collection just like any other objects in Javascript.

So, if you do:

var p = somePromiseReturningFunction();

p.then(f1).then(f2).then(f3);

Then, p will remain around until somePromiseReturningFunction() is done with any references to the promise that it returned (usually, though not always, this occurs when the promise is finally fulfilled or rejected) and when the variable p goes out of scope. If p never goes out of scope (like when it's global or in some other lasting scope), then it will remain forever (just like any other Javascript object).


There are some misconceptions in your question so let me attempt to square those up.

You're using the construct p = p.then(dosomething(x)); which is likely not correct. You need to pass .then() a function reference. So, unless you want doSomething(x) to execute immediately and it also returns another function that is what you want called as then .then() handler (which seems unlikely here), then this is not the right construct. You probably meant to have:

p = p.then(result => dosomething(x));

or in ES5 syntax:

p = p.then(function(result) {
    return dosomething(x)
});

You show the same issue in this too:

return p.then(finalthing()).catch(pretendnobadthing());

which should probably be:

return p.then(finalthing).catch(pretendnobadthing);

Remember, when you use f(), that means to execute f immediately. When you just pass f, that passes a function reference which the underlying function/method you are passing it to can then call later at the time of its choosing which is what you want for .then() and .catch() handlers.

What I really don't understand is that if you do something that takes a large amount of time in the main code, wouldn't that mean that one of the promises could complete before the main code actually got round to to setting up the .then.

First off, my original explanation at the beginning of my answer should explain that calling .then() on an already resolved promise is perfectly fine so this isn't an issue at all. It will just schedule action on the next tick of the event loop.

But, that isn't even the case here because Javascript in the browser and node.js is single-threaded so while your long-running code is running, that promise (who's async action was previously started) can't yet get resolved. Though the underlying async operation may be done and an event may be sitting in the Javascript event queue that will trigger a callback that will resolve the promise, that event in the event queue won't get processed until the current piece of Javascript that is executing is done and returns control to the system.

Upvotes: 3

Ry-
Ry-

Reputation: 224942

What I really don't understand is that if you do something that takes a large amount of time in the main code, wouldn't that mean that one of the promises could complete before the main code actually got round to to setting up the .then.

Since common implementations of JavaScript never run it in parallel, no. Nevertheless,

Can you always use .then/.catch on a promise, and if its already reached completion, it'll carry on from the point it got to, so it conceptually looks like a big chain that will run to completion at some indeterminate point?

yes! This is a big advantage of promises. Take this for example:

var promise = Promise.resolve(5); // already resolved here

setTimeout(function () {
    promise.then(function (x) {
        console.log(x); // still logs 5
    });
}, 1000);

And if so, doesn't that mean every time you create a promise chain it'll hang about for ever?

Until it’s resolved, yes, but if the promise has already been resolved and there are no ways to reference it anymore, it can be disposed of like any other object.

Upvotes: 0

Related Questions