7

I am learning Promise, in order to understand it I read a bit about Event loop of JavaScript. This article briefly introduced the working of event loop such as call stack, event table and message queue.

But I don't know how the call stack deal with the line containing 'return', and what happens thereafter. Below is an example that I wrote to hopefully understand how Promise works based on event loop. Also see http://jsbin.com/puqogulani/edit?js,console if you want to give it a go.

var p1 = new Promise(
  function(resolve, reject){
    resolve(0);
});

p1.then(function(val){
  console.log(val);
  p1.then(function(){
    console.log("1.1.1");
    p1.then(function(){
      console.log("1.1.2");
      p1.then(function(){
        console.log("1.1.3");
      });
    });
  });

  p1.then(function(){
    console.log("1.2");
  })

  return 30;

  //return new Promise(function(resolve, reject){
  //  resolve(30);
  //});

})
  .then(function(val){
  console.log(val/2);
});

p1.then(function(){
  console.log("2.1");
});

console.log("Start");

As can be seen, there are two "return", using each of them will give a different output order. Specifically, when using return 30;, 1.1.2, 1.1.3 are after 15, but when using return new Promise(...), 1.1.2, 1.1.3 are before 15. So what exactly happened when the code reached two different 'return'?

try-catch-finally
  • 7,436
  • 6
  • 46
  • 67
Junlong Wang
  • 418
  • 4
  • 13
  • 1
    Removed the callbacks of 1.1.4 and 1.1.5 - they're not referred to and make it not easier to understand and everyone gets the point up to 1.1.2 already. Everyone will still be able to understand the dilemma without them. – try-catch-finally Jan 22 '17 at 19:07
  • @try-catch-finally, Thanks for your suggestion and editing. I just wanted to make it clearer, though sometimes it makes the opposite :) – Junlong Wang Jan 22 '17 at 19:19
  • So @try-catch-finally, I am wondering if you could give advices on my question? Thank you – Junlong Wang Jan 22 '17 at 19:46
  • I was trying to reduce that example to a bare minimum, predicting the behavior, testing it and building it up step by step. I don't feel 100% confident to give you a useful answer. But I'd say it's because an already resolved Promise will call its handlers synchronously (that is the call of `p1.then()` within its very first handler - though it was resolved async, initially, now the remaining handlers will be called sync) while a Promise is always resolved async thus giving the stack established in `p1` time to finish calling the handlers. General advice: don't `p1.then()` inside `p1.then()` :/ – try-catch-finally Jan 22 '17 at 20:35
  • 1
    http://2014.jsconf.eu/speakers/philip-roberts-what-the-heck-is-the-event-loop-anyway.html and https://bevacqua.github.io/promisees/ – coma Jan 22 '17 at 21:33
  • @coma, Thanks for your reply. I'll watch the video you mentioned later, but the visualisation you advised is really obvious and acceptable, very nice, thank you! – Junlong Wang Jan 23 '17 at 02:19
  • @try-catch-finally, do you mean returning a new Promise is async so that it takes more time than returning an existed value like '30', thus caused '15' show up later than ''1.1.3"? Besides, would you mind explaining more how many steps are needed for call stack and event table to process "return new Promise(...)"? Thank you – Junlong Wang Jan 23 '17 at 02:24

1 Answers1

2

The difference is described in http://promisesaplus.com/ under the promise resolution procedure.

For the first return value:

2.3.3.4 If then is not a function, fulfill promise with x.

For the second:

2.3.2 If x is a promise, adopt its state [3.4]:

2.3.2.2 If/when x is fulfilled, fulfill promise with the same value.

We can see this implemented q.js. This is one possible implementation but seems to explain the delay:

function coerce(promise) {
    var deferred = defer();
    Q.nextTick(function () {
        try {
            promise.then(deferred.resolve, deferred.reject, deferred.notify);
        } catch (exception) {
            deferred.reject(exception);
        }
    });
    return deferred.promise;
}

When returning a promise from the then function, we have two separate promise objects: the one returned from the function passed to then, and the one returned from then. These need to be connected together so that resolving the first, resolves the second. This is done with promise.then(deferred.resolve, ...)

The first delay comes from Q.nextTick. This executes the function on the next iteration of the event loop. There's some discussion in the commit comments on why it's needed.

Calling promise.then adds a further delay of one iteration of the event loop. As required in the spec:

2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].

The execution would go something like:

p1.then with function containing 1.1.1 is called
    function containing 1.1.1 is queued
p1.then with function containing 1.2 is called
    function containing 1.2 is queued
Promise resolving to 30 is returned
    Q.nextTick function is queued
----------
1.1.1 is printed
p1.then with function containing 1.1.2 is called
    function containing 1.1.2 is queued
1.2 is printed
Q.nextTick function is executed
    promise.then(deferred.resolve, ...) is queued
----------
1.1.2 is printed
p1.then with function containing 1.1.3 is called
    function containing 1.1.3 is queued
promise.then(deferred.resolve, ...) is executed
    function containing val/2 is queued
----------
1.1.3 is printed
val/2 is printed
Community
  • 1
  • 1
fgb
  • 18,439
  • 2
  • 38
  • 52