This commit is contained in:
Ilya Kantor 2019-08-07 17:31:14 +03:00
parent e80667391f
commit 8bd8e90488
5 changed files with 36 additions and 191 deletions

View file

@ -48,24 +48,6 @@ The whole thing works, because a call to `promise.then` returns a promise, so th
When a handler returns a value, it becomes the result of that promise, so the next `.then` is called with it.
To make these words more clear, here's the start of the chain:
```js run
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result);
return result * 2; // <-- (1)
}) // <-- (2)
// .then…
```
The value returned by `.then` is a promise, that's why we are able to add another `.then` at `(2)`. When the value is returned in `(1)`, that promise becomes resolved, so the next handler runs with the value.
**A classic newbie error: technically we can also add many `.then` to a single promise. This is not chaining.**
For example:
@ -138,9 +120,9 @@ new Promise(function(resolve, reject) {
});
```
Here the first `.then` shows `1` and returns `new Promise(…)` in the line `(*)`. After one second it resolves, and the result (the argument of `resolve`, here it's `result*2`) is passed on to handler of the second `.then` in the line `(**)`. It shows `2` and does the same thing.
Here the first `.then` shows `1` and returns `new Promise(…)` in the line `(*)`. After one second it resolves, and the result (the argument of `resolve`, here it's `result * 2`) is passed on to handler of the second `.then`. That handler is in the line `(**)`, it shows `2` and does the same thing.
So the output is again 1 -> 2 -> 4, but now with 1 second delay between `alert` calls.
So the output is the same as in the previous example: 1 -> 2 -> 4, but now with 1 second delay between `alert` calls.
Returning promises allows us to build chains of asynchronous actions.
@ -184,7 +166,7 @@ Here each `loadScript` call returns a promise, and the next `.then` runs when it
We can add more asynchronous actions to the chain. Please note that code is still "flat", it grows down, not to the right. There are no signs of "pyramid of doom".
Please note that technically we can add `.then` directly to each `loadScript`, like this:
Technically, we could add `.then` directly to each `loadScript`, like this:
```js run
loadScript("/article/promise-chaining/one.js").then(script1 => {
@ -207,7 +189,7 @@ Sometimes it's ok to write `.then` directly, because the nested function has acc
````smart header="Thenables"
To be precise, `.then` may return a so-called "thenable" object - an arbitrary object that has method `.then`, and it will be treated the same way as a promise.
To be precise, a handler may return not exactly a promise, but a so-called "thenable" object - an arbitrary object that has method `.then`, and it will be treated the same way as a promise.
The idea is that 3rd-party libraries may implement "promise-compatible" objects of their own. They can have extended set of methods, but also be compatible with native promises, because they implement `.then`.
@ -227,7 +209,9 @@ class Thenable {
new Promise(resolve => resolve(1))
.then(result => {
*!*
return new Thenable(result); // (*)
*/!*
})
.then(alert); // shows 2 after 1000ms
```
@ -242,7 +226,7 @@ This feature allows to integrate custom objects with promise chains without havi
In frontend programming promises are often used for network requests. So let's see an extended example of that.
We'll use the [fetch](info:fetch) method to load the information about the user from the remote server. It has a lot of optional parameters covered in separate chapters, but the basic syntax is quite simple:
We'll use the [fetch](info:fetch) method to load the information about the user from the remote server. It has a lot of optional parameters covered in [separate chapters](info:fetch), but the basic syntax is quite simple:
```js
let promise = fetch(url);
@ -259,7 +243,7 @@ fetch('/article/promise-chaining/user.json')
// .then below runs when the remote server responds
.then(function(response) {
// response.text() returns a new promise that resolves with the full response text
// when we finish downloading it
// when it loads
return response.text();
})
.then(function(text) {
@ -276,7 +260,7 @@ We'll also use arrow functions for brevity:
// same as above, but response.json() parses the remote content as JSON
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => alert(user.name)); // iliakan
.then(user => alert(user.name)); // iliakan, got user name
```
Now let's do something with the loaded user.
@ -317,7 +301,7 @@ fetch('/article/promise-chaining/user.json')
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
*!*
.then(githubUser => new Promise(function(resolve, reject) {
.then(githubUser => new Promise(function(resolve, reject) { // (*)
*/!*
let img = document.createElement('img');
img.src = githubUser.avatar_url;
@ -327,7 +311,7 @@ fetch('/article/promise-chaining/user.json')
setTimeout(() => {
img.remove();
*!*
resolve(githubUser);
resolve(githubUser); // (**)
*/!*
}, 3000);
}))
@ -335,9 +319,11 @@ fetch('/article/promise-chaining/user.json')
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
```
Now right after `setTimeout` runs `img.remove()`, it calls `resolve(githubUser)`, thus passing the control to the next `.then` in the chain and passing forward the user data.
That is, `.then` handler in the line `(*)` now returns `new Promise`, that becomes settled only after the call of `resolve(githubUser)` in `setTimeout` `(**)`.
As a rule, an asynchronous action should always return a promise.
The next `.then` in chain will wait for that.
As a good rule, an asynchronous action should always return a promise.
That makes it possible to plan actions after it. Even if we don't plan to extend the chain now, we may need it later.