Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding JS Promises

I would like to get a deeper understanding of how Promises work internally. Therefore I have some sample code:

var p1 = new Promise(
  function(resolve, reject) {
    window.setTimeout(
      function() {
        resolve('res called')
      }, 2000);
  });


var p2 = new Promise(
  function(resolve, reject) {
    window.setTimeout(
      function() {
        resolve('res called')
      }, 2000);
  });


function chainPromises() {
  return p1.then(function(val) {
    console.log("p1");
    return p2.then(function(val) {
      console.log("p2");
      return val;
    });
  });
}

chainPromises().then(function(val) {
  console.log(val);
});

Here a link to execute this code.

As you would predict, first p1 is resolved, afterwards p2 and in the end the final then prints the resolv value.

But the API ref states the following:

"then" returns a new promise equivalent to the value you return from onFulfilled/onRejected after being passed through Promise.resolve

So it would be interesting to know WHEN exactly the "then" function is executed? Because the final "then" in the code is chained to the chainPromises(), I first thought that it would execute after the function chainPromises() returns something (in this case another promise).

If this would have been the case the "val" of the final "then" function would be the returned promise. But instead, the final "then" waits until all promises inside the first "then" which are returned have been resolved. This absolutely makes sense because in this way, the "then" functions can be stacked, but I do not really get how this is done, since the API spec. does not really cover what "then" returns and when the "then" functions is executed.

Or in other words, why does the final "then" function wait until all the Promises are resolved inside the chainPromises() function instead of just waiting for the first returned object as the API doc says.

I hope I could make clear what I mean.. :)

like image 909
Quick n Dirty Avatar asked Dec 24 '14 13:12

Quick n Dirty


People also ask

How do you explain promises in JavaScript?

What is a promise in JavaScript? JavaScript is single threaded, meaning that two bits of script cannot run at the same time; they have to run one after another. A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.

How do you understand Promise?

A promise gives you an assurance that something will be done. Whether they (who made the promise) will do it themselves or they get it done by others is immaterial. They give you an assurance, based on which you can plan something. A promise can either be kept or broken.

What is Promise in JavaScript for beginners?

A promise is simply a placeholder for an asynchronous task which is yet to be completed. When you define a promise object in your script, instead of returning a value immediately, it returns a promise.

What is the difference between a Promise and a callback?

Callbacks are functions passed as arguments into other functions to make sure mandatory variables are available within the callback-function's scope. Promises are placeholder objects for data that's available in the future.


2 Answers

About Promise resolution

The thing you're witnessing here is called recursive thenable resolution. The promise resolution process in the Promises/A+ specification contains the following clause:

onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x)

The ES6 promise specification (promises unwrapping) contains a similar clause.

This mandates that when a resolve operation occurs: either in the promise constructor, by calling Promise.resolve or in your case in a then chain a promise implementation must recursively unwrap the returned value if it is a promise.

In practice

This means that if onFulfilled (the then) returns a value, try to "resolve" the promise value yourself thus recursively waiting for the entire chain.

This means the following:

promiseReturning().then(function(){
    alert(1);
    return foo(); // foo returns a promise
}).then(function(){
    alert(2); // will only run after the ENTIRE chain of `foo` resolved
              // if foo OR ANY PART OF THE CHAIN rejects and it is not handled this 
              // will not run
});

So for example:

promiseReturning().then(function(){
    alert(1);
    return Promise.resolve().then(function(){ throw Error(); });
}).then(function(){
    alert("This will never run");
});

And that:

promiseReturning().then(function(){
    alert(1);
    return Promise.resolve().then(function(){ return delay(2000); });
}).then(function(){
    alert("This will only run after 2000 ms");
});

Is it a good idea?

It's been the topic of much debate in the promises specification process a second chain method that does not exhibit this behavior was discussed but decided against (still available in Chrome, but will be removed soon). You can read about the whole debate in this esdiscuss thread. This behavior is for pragmatic reasons so you wouldn't have to manually do it.

In other languages

It's worth mentioning that other languages do not do this, neither futures in Scala or tasks in C# have this property. For example in C# you'd have to call Task.Unwrap on a task in order to wait for its chain to resolve.

like image 172
Benjamin Gruenbaum Avatar answered Oct 01 '22 17:10

Benjamin Gruenbaum


Let's start with an easy perspective: "chainPromises" returns a promise, so you could look at it this way:

// Do all internal promises
var cp = chainPromises();

// After everything is finished you execute the final "then".
cp.then(function(val) {
    console.log(val);
});

Generally speaking, when returning a promise from within a "then" clause, the "then" function of the encapsulating promise will be marked as finished only after the internal "then" has finished.

So, if "a" is a promise, and "b" is a promise:

// "a"'s "then" function will only be marked as finished after "b"'s "then" function has finished.  
var c = a.then(function () {
    return b.then(function () {
        console.log("B!");
    };
};

// c is a promise, since "then" always returns a promise.    
c.then(function() {
    console.log("Done!");
};

So the output will be:

B! 
Done!

Notice btw, that if you don't "return" the internal promise, this will not be the case:

// "a"'s "then" function will only be marked as finished without waiting for "b"'s "then" to finish.  
var c = a.then(function () {
    // Notice we're just calling b.then, and don't "return" it. 
    b.then(function () {
        console.log("B!");
    };
};

// c is a promise, since "then" always returns a promise.    
c.then(function() {
    console.log("Done!");
};

Here we can't know what would be outputted first. It could be either "B!" or "Done!".

like image 42
dcoder Avatar answered Oct 01 '22 17:10

dcoder