0

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?

Tom Tanner
  • 9,244
  • 3
  • 33
  • 61
  • @jfriend00 not necessary. – nicovank Feb 12 '17 at 22:25
  • 1
    if `dosomething()` and `finalthing()` return a **function** that is to be called, then perhaps that code is right ... but, I doubt that's the case here – Jaromanda X Feb 12 '17 at 22:26
  • @jfriend00 exactly what Jaromanda X said. [see this question](http://stackoverflow.com/questions/111102/how-do-javascript-closures-work) – nicovank Feb 12 '17 at 22:58
  • Please explain why this is unclear and I'll try to clarify (though it seems to have been clear enough to jfriend00) – Tom Tanner Feb 13 '17 at 07:38

2 Answers2

3

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.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • @Bergi - I imagine you are technically correct, but please explain to me why rejecting a promise puts it in the `rejected` state, but resolving a promise puts it in the `fulfilled` state (not in the resolved state). That seems like a rather unfortunate choice of terminology that really confuses people, including relatively experienced people like me and even more so less experienced people. Why can't we speak of a promise as pending, resolved or rejected? – jfriend00 Feb 13 '17 at 05:19
  • @Bergi - Note, the ES6 spec for the promise constructor says the executor passed to the constructor is a function that takes two arguments a resolve function and a reject function. So, the ES6 spec is referring to the verbs as to "resolve" and to "reject" the promise. That's where we all get started with these names for these actions. So, it ends up making no sense that the resolve function changes the state to be fulfilled, not resolved. I'm always confused by this. – jfriend00 Feb 13 '17 at 05:21
  • Fulfilling a promise puts it in the fulfilled state. "Resolving" does not always fulfill a promise, you can also resolve it to another (possibly rejected) promise. See also [here](http://stackoverflow.com/a/41910860/1048572). – Bergi Feb 13 '17 at 05:24
  • @Bergi - Terminology in the answer updated, but I am not convinced that this makes the answer easier for the OP to understand and follow. I've seen your other reference and I get the subtle distinction, but I still think this is an unfortunate mess that the creators of promises made and it makes things more difficult than they need be to explain and talk about. – jfriend00 Feb 13 '17 at 05:28
  • @Bergi - Actually, [this answer of yours](http://stackoverflow.com/a/29269515/816620) is of more help to me. That essentially says there are really four states: pending, rejected, fulfilled as we've been discussing and the fourth is resolved which means this promise is tracking some other promise and it will eventually go to fulfilled or rejected based on what happens to the promise it is tracking. Is that not correct? Now, if only I can actually commit this to memory (been trying for multiple years now which I think says something about the unfortunately ways terms are being used). – jfriend00 Feb 13 '17 at 05:35
  • Thank you. Just to clarify the functions in question in the code I'm looking at do return promises but it'd have been tedious to write that and I haven't caught up with ES6 yet. But also thanks for explaining the difference. – Tom Tanner Feb 13 '17 at 07:42
  • @TomTanner - `p.then(finalthing())` is still wrong even if `finalThing()` returns a promise. You would need to do `p.then(function(result) { return finalThing()})` or in ES6 syntax, `p.then(result => finalThing())`. The point is, no matter what `finalthing()` returns, you don't want to call it immediately like you were doing. – jfriend00 Feb 13 '17 at 07:56
  • actually, looks like my code was doing the right thing (with function() { }), it just never got into the example – Tom Tanner Feb 13 '17 at 19:44
0

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.

Ry-
  • 218,210
  • 55
  • 464
  • 476