en.javascript.info/8-async/03-promise-chaining/article.md
Ilya Kantor 8134937fc3 working
2017-04-17 14:20:01 +02:00

5.1 KiB

Promises chaining

Let's formulate the problem mentioned in the chapter info:callback-hell:

  • We have a sequence of tasks to be done one after another. For instance, loading scripts. The next task may need the result of the previous one.
  • How to code it well?

Promises can cover that need in two ways:

  1. Promises chaining.
  2. Async functions.

Let's see the first way in this chapter and the second one in the next.

Promises chaining looks like this:

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000);

}).then(function(result) {

  alert(result); // 1
  return result * 2;

}).then(function(result) {

  alert(result); // 2
  return result * 2;

}).then(function(result) {

  alert(result); // 4
  return result * 2;

});
// ...

As we can see, a call to promise.then returns a promise, that we can use again for .then. A value returned by .then becomes a result in the next .then. So in the example above we have a sequence of results: 1 -> 2 -> 4.

Please note that chaining .then is not the same as many .then on a single promise, like below:

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

In the code above, all .then are on the same promise, so all of them get the same result -- the result of that promise. And all alert show the same: 1.

If we want to use the value returned by a handler of .then, then we should add a new .then after it (to chain).

Returning promises

Normally, the value returned by a handler is passed to the next .then. But there's an exception. If the returned value is a promise, then further execution is suspended till it settles. And then the result of that promise is used.

Let's see it in action here:

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000);

}).then(function(result) {

  alert(result); // 1

*!*
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });
*/!*

}).then(function(result) {

  alert(result); // 2

*!*
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });
*/!*

}).then(function(result) {

  alert(result); // 4

});

Now we have the same 1 -> 2 > 4 output, but with 1 second delay between each.

When we return new Promise(…), the next .then in the chain is executed when it settles and gets its result.

Let's use it to loadScript multiple scripts one by one:

function loadScript(src) {  
  return new Promise(function(resolve, reject) {
    let script = document.createElement('script');
    script.src = src;

    script.onload = () => resolve(script);
    script.onerror = () => reject(new Error("Script load error: " + src));

    document.head.append(script);
  });
}

loadScript("/article/promise-chaining/one.js")
  .then(function(script) {
    return loadScript("/article/promise-chaining/two.js");
  })
  .then(function(script) {
    return loadScript("/article/promise-chaining/three.js");
  })
  .then(function(script) {
    // use variables declared in scripts
    // to show that they indeed loaded
    alert("Done: " + (one + two + three));
  });

The code totally evades the pyramid of doom. We can add more asynchronous actions to the chain, and the code is still "flat".

Inheriting from promise, thenables, error handling?

An object that has a method called .then is called a "thenable".

Instead of checking if something is instanceof Promise, we should usually check it for being thenable, and if it is, then treat it as a promise ("duck typing").

JavaScript specification also checks the value returned by a handler for being a thenable, not exactly a promise, when it decides whether to pass it along the chain or wait for the result. So in the examples above we could use custom thenables instead of Promise instances.

For instance, native promises give no way to "abort" the execution. The loadScript above cannot "cancel" script loading, just because there's no .abort method on promises, we can only listen for the state change using .then/catch.

Let's

Error handling

new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
}).then(function(result) {

  throw new Error("Woops!");

}).catch(function(error) {

  alert(error.message); // Woops!

});

The idea is :

  • A callback in .then may return a result.

One of main purposes of promises is to make asyn The main purpose of promises Promises

Promises can be chained. That allows actions to follow one after another.

Here's a simple example first:

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve(""))
})



What if we want to
The main idea behind promises
Promises can be used for asynchronous tasks that eventually finish with a result or an error.

We already have `loadScript`