Merge commit 'e456067d3d' into en

* commit 'e456067d3d':
  up
  up
This commit is contained in:
Thierry Parmentelat 2017-05-23 14:57:09 +02:00
commit 7c4e35648d
27 changed files with 453 additions and 16988 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Before After
Before After

View file

@ -1,8 +1,10 @@
# Callback hell
Consider this function `loadScript(src)` that loads a script: # Introduction: sync vs async, callbacks
Many actions in Javascript are *asynchronous*.
For instance, take a look at the function `loadScript(src)` that loads a script:
```js ```js
function loadScript(src) { function loadScript(src) {
@ -12,7 +14,7 @@ function loadScript(src) {
} }
``` ```
When the script element is added to the document, the browser loads it and executes. So, the function works. The purpose of the function is to load a new script. When it adds the `<script src="…">` to the document, the browser loads and executes it.
We can use it like this: We can use it like this:
@ -21,27 +23,28 @@ We can use it like this:
loadScript('/my/script.js'); loadScript('/my/script.js');
``` ```
The function is *asynchronous*: the script starts loading now, but finishes later. The function is called "asynchronous", because the action (script loading) finishes not now, but later.
"Synchonous" and "asynchronous" are general programming terms, not specific to JavaScript. The call initiates the script loading, then the execution continues normally.
A *synchronous* action suspends the execution until it's completed. For instance, a call to `alert` or `prompt` is synchronous: the program may not continue until it's finished.
```js
let age = prompt("How old are you", 20);
// the execution of the code below awaits for the prompt to finish
// the script hangs
```
An *asynchronous* action allows the program to continue while it's in progress. For instance, a call to `loadScript` is asynchronous. It initiates the script loading, but does not suspend the execution. Other commands may execute while the script is loading:
```js ```js
loadScript('/my/script.js'); loadScript('/my/script.js');
// the execution of the code below *does not* wait for the script loading to finish, // the code below doesn't wait for the script loading to finish
// it just continues
``` ```
As of now, the `loadScript` function loads the script, doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when happens, to use additional functions and variables from that script. Now let's say we want to use the new script when loads. It probably declares new functions, so we'd like to run them.
...But if we do that immediately after the `loadScript(…)` call, that wouldn't work:
```js
loadScript('/my/script.js'); // the script has "function newFunction() {…}"
*!*
newFunction(); // no such function!
*/!*
```
Naturally, the browser probably didn't have the time to load the script. As of now, the `loadScript` function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when happens, to use new functions and variables from that script.
Let's add a `callback` function as a second argument to `loadScript` that should execute when the script loads: Let's add a `callback` function as a second argument to `loadScript` that should execute when the script loads:
@ -58,7 +61,19 @@ function loadScript(src, callback) {
} }
``` ```
Now we're able to load a script and then run our code that can use new functions from it, like here: Now if we want to call new functions from the script, we should write that in the callback:
```js
loadScript('/my/script.js', function() {
// the callback runs after the script is loaded
newFunction(); // so now it works
...
});
```
That's the idea: the second argument is a function (usually anonymous) that runs when the action is completed.
Here's a runnable example with the real script:
```js run ```js run
function loadScript(src, callback) { function loadScript(src, callback) {
@ -69,7 +84,7 @@ function loadScript(src, callback) {
} }
*!* *!*
loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', function(script) { loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => {
alert(`Cool, the ${script.src} is loaded`); alert(`Cool, the ${script.src} is loaded`);
alert( _ ); // function declared in the loaded script alert( _ ); // function declared in the loaded script
}); });
@ -78,13 +93,13 @@ loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', f
That's called a "callback-based" style of asynchronous programming. A function that does something asynchronously should provide a `callback` argument where we put the function to run after it's complete. That's called a "callback-based" style of asynchronous programming. A function that does something asynchronously should provide a `callback` argument where we put the function to run after it's complete.
Here it was used for `loadScript`, but of course it's a general approach. Here we did so in `loadScript`, but of course it's a general approach.
## Callback in callback ## Callback in callback
What if we need to load two scripts sequentially: the first one, and then the second one after it? How to load two scripts sequentially: the first one, and then the second one after it?
We can put the second `loadScript` inside the callback, like this: The natural solution would be to put the second `loadScript` call inside the callback, like this:
```js ```js
loadScript('/my/script.js', function(script) { loadScript('/my/script.js', function(script) {
@ -100,7 +115,7 @@ loadScript('/my/script.js', function(script) {
}); });
``` ```
Now after the outer `loadScript` is complete, the callback initiates the inner one. After the outer `loadScript` is complete, the callback initiates the inner one.
...What if we want one more script? ...What if we want one more script?
@ -120,11 +135,9 @@ loadScript('/my/script.js', function(script) {
}); });
``` ```
As we can see, a new asynchronous action means one more nesting level. So the code becomes deeper and deeper.
## Handling errors ## Handling errors
In examples above we didn't consider errors. What if a script loading failed with an error? Our callback should be able to react on that. In examples above we didn't consider errors. What if a script loading fails with an error? Our callback should be able to react on that.
Here's an improved version of `loadScript` that tracks loading errors: Here's an improved version of `loadScript` that tracks loading errors:
@ -144,7 +157,7 @@ function loadScript(src, callback) {
It calls `callback(null, script)` for successful load and `callback(error)` otherwise. It calls `callback(null, script)` for successful load and `callback(error)` otherwise.
Usage: The usage:
```js ```js
loadScript('/my/script.js', function(error, script) { loadScript('/my/script.js', function(error, script) {
if (error) { if (error) {
@ -155,11 +168,13 @@ loadScript('/my/script.js', function(error, script) {
}); });
``` ```
Once again, the recipe that we used for `loadScript` is actually quite common. It's called the "error-first callback" style.
The convention is: The convention is:
1. The first argument of `callback` is reserved for an error if it occurs. Then `callback(err)` is called. 1. The first argument of `callback` is reserved for an error if it occurs. Then `callback(err)` is called.
2. The second argument and successive ones if needed are for the successful result. Then `callback(null, result1, result2…)` is called. 2. The second argument and successive ones if needed are for the successful result. Then `callback(null, result1, result2…)` is called.
So the single `callback` function is used both for reporting errors and passing back results. That's called "error-first callback" style. Or we could use different functions for successful and erroneous completion. So the single `callback` function is used both for reporting errors and passing back results.
## Pyramid of doom ## Pyramid of doom
@ -200,17 +215,17 @@ In the code above:
2. We load `2.js`, then if there's no error. 2. We load `2.js`, then if there's no error.
3. We load `3.js`, then if there's no error -- do something else `(*)`. 3. We load `3.js`, then if there's no error -- do something else `(*)`.
As calls become more nested, the whole thing becomes increasingly more difficult to manage, especially if we add real code instead of `...`, that may include more loops, conditional statements and other usage of loaded scripts. As calls become more nested, the code becomes deeper and increasingly more difficult to manage, especially if we have a real code instead of `...`, that may include more loops, conditional statements and so on.
That's sometimes called "callback hell" or "pyramid of doom". That's sometimes called "callback hell" or "pyramid of doom".
![](callback-hell.png) ![](callback-hell.png)
The pyramid grows to the right with every asynchronous action. Soon it spirales out of control. The "pyramid" of nested calls grows to the right with every asynchronous action. Soon it spirales out of control.
So this way of coding appears not so good. So this way of coding appears not so good.
In simple cases we can evade the problem by making every action a standalone function, like this: We can try to alleviate the problem by making every action a standalone function, like this:
```js ```js
loadScript('1.js', step1); loadScript('1.js', step1);
@ -242,10 +257,10 @@ function step3(error, script) {
}; };
``` ```
See? It does the same, and there's no deep nesting now, because we moved every function to the top. But the code looks like a torn apart spreadsheet. We need to eye-jump between pieces while reading it. It's not very readable, especially if you are not familiar with it and don't know where to eye-jump. See? It does the same, and there's no deep nesting now, because we made every action a separate top-level function. It works, but the code looks like a torn apart spreadsheet. It's difficult to read. One needs to eye-jump between pieces while reading it. That's inconvenient, especially the reader is not familiar with the code and doesn't know where to eye-jump.
Also the functions `step*` have no use, they are only created to evade the "pyramid of doom". Also the functions named `step*` are all of a single use, they are only created to evade the "pyramid of doom". So there's a bit of a namespace cluttering here.
So we'd like to have a better way of coding for complex asynchronous actions. We'd like to have a better way of coding for complex asynchronous actions.
Luckily, there are other ways to evade such pyramids. For instance, we can use "promises", described in the next chapter. Luckily, there are other ways to evade such pyramids. One of the best ways is to use "promises", described in the next chapter.

View file

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Before After
Before After

View file

@ -2,19 +2,19 @@
Imagine that you're a top singer, and fans ask for your next upcoming single day and night. Imagine that you're a top singer, and fans ask for your next upcoming single day and night.
You make a promise to send it to them when it's published. You give your fans a list. They can fill in their coordinates, so that when the song becomes available, all subscribed parties instantly get it. And they also can leave a note that if something goes very wrong, so that the song won't be published ever, then they are to be notified. To get a relief, you promise to send it to them when it's published. You give your fans a list. They can fill in their coordinates, so that when the song becomes available, all subscribed parties instantly get it. And if something goes very wrong, so that the song won't be published ever, then they are also to be notified.
Everyone is happy: you, because people don't crowd you any more, and fans, because they won't miss the single. Everyone is happy: you, because the people don't crowd you any more, and fans, because they won't miss the single.
That was a real-life analogy for things we often have in programming: That was a real-life analogy for things we often have in programming:
1. A "producing code" does something that needs time. For instance, it loads a remote script. That's the "singer". 1. A "producing code" that does something and needs time. For instance, it loads a remote script. That's the "singer".
2. A "consuming code" wants the result when it's ready. Many functions may need that result. So that's the "fans". 2. A "consuming code" wants the result when it's ready. Many functions may need that result. So that's the "fans".
3. A *promise* is a special JavaScript object that links them together. That's the "list". The singer makes it and gives to fans, so that they can subscribe. 3. A *promise* is a special JavaScript object that links them together. That's the "list". The producing code makes it and gives to everyone, so that they can subscribe for the result.
The analogy is not precise: promises are more complex than a simple list. They have additional features and limitations. But still they are alike. The analogy isn't very accurate, because JavaScript promises are more complex than a simple list: they have additional features and limitations. But still they are alike.
The constructor syntax is: The constructor syntax for a promise object is:
```js ```js
let promise = new Promise(function(resolve, reject) { let promise = new Promise(function(resolve, reject) {
@ -22,23 +22,26 @@ let promise = new Promise(function(resolve, reject) {
}); });
``` ```
The function passed to `new Promise` is called *executor*. When the promise is created, it's called automatically. It contains the producing code, that should eventually finish with a result. The executor is a "singer". The function passed to `new Promise` is called *executor*. When the promise is created, it's called automatically. It contains the producing code, that should eventually finish with a result. In terms of the analogy above, the executor is a "singer".
The `promise` object has internal properties: The `promise` object has internal properties:
- `state` -- initially is "pending", then changes to "fulfilled" or "rejected", - `state` -- initially is "pending", then changes to "fulfilled" or "rejected",
- `result` -- an arbitrary value, initially `undefined`. - `result` -- an arbitrary value, initially `undefined`.
When the executor finishes the job, it should change the state of the promise by calling one of: Both `state` and `result` are managed by the executor.
- `resolve(value)` -- to indicate that the job finished successfully: sets `state` to `"fulfilled"`, and `result` to `value`. When the executor finishes the job, it should call one of:
- `reject(error)` -- to indicate that an error occured: sets `state` to `"rejected"` and `result` to `error`.
- `resolve(value)` -- to indicate that the job finished successfully:
- sets `state` to `"fulfilled"`,
- sets `result` to `value`.
- `reject(error)` -- to indicate that an error occured:
- sets `state` to `"rejected"`,
- sets `result` to `error`.
![](promise-resolve-reject.png) ![](promise-resolve-reject.png)
The `promise` object can be used to subscribe to the result (like a "list"), soon we'll cover that, but first let's see few code examples of `new Promise`.
Here's a simple executor, just to see it in action: Here's a simple executor, just to see it in action:
```js run ```js run
@ -47,6 +50,9 @@ let promise = new Promise(function(resolve, reject) {
alert(resolve); // function () { [native code] } alert(resolve); // function () { [native code] }
alert(reject); // function () { [native code] } alert(reject); // function () { [native code] }
// after 1 second signal that the job is done with the result "done!"
setTimeout(() => *!*resolve("done!")*/!*, 1000);
}); });
``` ```
@ -55,14 +61,7 @@ We can see two things by running the code above:
1. The executor is called automatically and immediately (by `new Promise`). 1. The executor is called automatically and immediately (by `new Promise`).
2. Executor arguments `resolve` and `reject` are functions that come from JavaScript engine. We don't need to create them (their string representation may vary between JavaScript engines). Instead the executor should call them when ready. 2. Executor arguments `resolve` and `reject` are functions that come from JavaScript engine. We don't need to create them (their string representation may vary between JavaScript engines). Instead the executor should call them when ready.
Now let's make the executor that thinks for 1 second and then produces a result: After one second of thinking it calls `resolve("done")` to produce the result:
```js
let promise = new Promise(function(resolve, reject) {
// after 1 second signal that the job is done with the result "done!"
setTimeout(() => *!*resolve("done!")*/!*, 1000);
});
```
![](promise-resolve-1.png) ![](promise-resolve-1.png)
@ -77,10 +76,10 @@ let promise = new Promise(function(resolve, reject) {
![](promise-reject-1.png) ![](promise-reject-1.png)
The promise that was either resolved or rejected is called "settled". The promise that is either resolved or rejected is called "settled", as opposed to a "pending" promise.
````smart header="There can be only one result or an error" ````smart header="There can be only one result or an error"
We must call only one `resolve` or `reject`. And that's final. The executor should call only one `resolve` or `reject`. And the promise state change is final.
All further calls of `resolve` and `reject` are ignored: All further calls of `resolve` and `reject` are ignored:
@ -93,34 +92,37 @@ let promise = new Promise(function(resolve, reject) {
}); });
``` ```
The idea is that a job done by the executor may have only one result or an error. In programming, there exist other data structures that allow many results, for instance streams and queues, but we don't cover them here, because they are not supported by JavaScript core, and they lack certain features that promises provide. The idea is that a job done by the executor may have only one result or an error. In programming, there exist other data structures that allow many "flowing" results, for instance streams and queues. They have their own advantages and disadvantages versus promises. They are not supported by JavaScript core and lack certain language features that promises provide, so we don't cover them here, to concentrate on promises.
Also we shouldn't call `resolve/reject` with more then one argument: those after the first one are ignored. If we want a complex result, we can return an object. Also we if we call `resolve/reject` with more then one argument -- only the first argument is used, the next ones are ignored.
```` ````
```smart header="Reject with `Error` objects" ```smart header="Reject with `Error` objects"
Technically we can call `resolve` and `reject` with any value. But it's recommended to use `Error` objects in `reject` (or inherited from them). The reasoning for that will become obvious soon. Technically we can call `reject` (just like `resolve`) with any type of argument. But it's recommended to use `Error` objects in `reject` (or inherit from them). The reasoning for that will become obvious soon.
``` ```
````smart header="Resolve/reject can be immediate" ````smart header="Resolve/reject can be immediate"
In practice an executor usually does something asynchronously, but it doesn't have to. We can call `resolve` or `reject` immediately, like this: In practice an executor usually does something asynchronously and calls `resolve/reject` after some time, but it doesn't have to. We can call `resolve` or `reject` immediately, like this:
```js ```js
let promise = new Promise(function(resolve, reject) { let promise = new Promise(function(resolve, reject) {
resolve(123); resolve(123); // immediately give the result: 123
}); });
``` ```
For instance, it happens when we start to do a job and then see that everything has already been done before. Technically that's fine: we have a resolved promise right now. For instance, it happens when we start to do a job and then see that everything has already been done. Technically that's fine: we have a resolved promise right now.
```` ````
````smart header="Promise properties are internal"
Properties of promise objects like `state` and `result` are internal. We can't directly access them from our code.
```
## Consumers: ".then" and ".catch" ## Consumers: ".then" and ".catch"
A promise object allows to add "consumers" -- handler functions to run when it resolves/rejects. A promise object serves as a link between the producing code (executor) and the consuming code. "Consumers" -- functions that want to receive the result/error can be registered using methods `promise.then` and `promise.catch`.
The syntax is: The syntax of `.then` is:
```js ```js
promise.then( promise.then(
@ -129,9 +131,9 @@ promise.then(
); );
``` ```
The first argument of `then` runs on `resolve` and gets the result, and the second one -- on `reject` and gets the error. The first function argument runs when the promise is resolved and gets the result, and the second one -- when it's rejected and gets the error.
For example: For instance:
```js run ```js run
let promise = new Promise(function(resolve, reject) { let promise = new Promise(function(resolve, reject) {
@ -147,6 +149,8 @@ promise.then(
); );
``` ```
In case of a rejection:
```js run ```js run
let promise = new Promise(function(resolve, reject) { let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("Whoops!")), 1000); setTimeout(() => reject(new Error("Whoops!")), 1000);
@ -182,33 +186,33 @@ let promise = new Promise((resolve, reject) => {
}); });
*!* *!*
// same as promise.then(null, alert) // .catch(f) is the same as promise.then(null, f)
promise.catch(alert); // shows "Error: Whoops!" after 1 second promise.catch(alert); // shows "Error: Whoops!" after 1 second
*/!* */!*
``` ```
So, `.catch(f)` is a complete analog of `.then(null, f)`, just a shorthand.
````smart header="On settled promises `then` runs immediately" ````smart header="On settled promises `then` runs immediately"
If the promise is pending, `.then/catch` handlers wait for the result. Otherwise, if the promise has already settled, they execute immediately: If a promise is pending, `.then/catch` handlers wait for the result. Otherwise, if a promise has already settled, they execute immediately:
```js run ```js run
let promise = new Promise(resolve => resolve("done!")); // immediately resolve // an immediately resolved promise
let promise = new Promise(resolve => resolve("done!"));
promise.then(alert); // done! (without delay) promise.then(alert); // done! (without a delay)
``` ```
That's good for jobs that may be both asynchronous (loading something) and synchronous (we already have it and can resolve right now). The handler is guaranteed to run in both cases. That's good for jobs that may sometimes require time and sometimes finish immediately. The handler is guaranteed to run in both cases.
```` ````
Now let's see more practical examples to see how promises can help us to write asynchronous code. Now let's see more practical examples to see how promises can help us in writing asynchronous code.
## Example: loadScript ## Example: loadScript
We have the `loadScript` function for loading a script from the previous chapter. We have the `loadScript` function for loading a script from the previous chapter.
Here's the callback-based variant: Here's the callback-based variant, just to remind it:
```js ```js
function loadScript(src, callback) { function loadScript(src, callback) {
@ -255,10 +259,10 @@ promise.then(script => alert('One more handler to do something else!'));
We can immediately see few benefits over the callback-based syntax: We can immediately see few benefits over the callback-based syntax:
```compare minus="Callbacks" plus="Promises" ```compare minus="Callbacks" plus="Promises"
- We must have `callback` function available when calling `loadScript`. So we must know what to do with the result *before* we make a call for it. - We must have a ready `callback` function when calling `loadScript`. In other words, we must know what to do with the result *before* `loadScript` is called.
- There can be only one callback. - There can be only one callback.
+ Promises allow us to code more naturally. First we run `loadScript`, and `.then` code what we do with the result. + Promises allow us to code more naturally. First we run `loadScript`, and `.then` code what we do with the result.
+ We can call `.then` as many times as we want. + We can call `.then` as many times as we want, at any point of time later.
``` ```
So promises give us better code flow and flexibility. But there's more. We'll see that in the next chapters. So promises already give us better code flow and flexibility. But there's more. We'll see that in the next chapters.

View file

@ -1,15 +1,20 @@
The short answer is: **no, they are not**: The short answer is: **no, they are not the equal**:
The difference is that if an error happens in `f1`, then it is handled by `.catch` here: The difference is that if an error happens in `f1`, then it is handled by `.catch` here:
```js run ```js run
promise.then(f1).catch(f2); promise
.then(f1)
.catch(f2);
``` ```
...But not here: ...But not here:
```js run ```js run
promise.then(f1, f2); promise
.then(f1, f2);
``` ```
That's because an error/result is passed down the chain, and in the second code piece there's no chain below. That's because an error is passed down the chain, and in the second code piece there's no chain below `f1`.
In other words, `.then` passes results/errors to the next `.then/catch`. So in the first example, there's a `catch` below, and in the second one -- there isn't, so the error is unhandled.

View file

@ -1,4 +1,4 @@
# Promise then vs catch # Promise: then versus catch
Are these code fragments equal? In other words, do they behave the same way in any circumstances, for any handler functions? Are these code fragments equal? In other words, do they behave the same way in any circumstances, for any handler functions?

View file

@ -1,6 +1,6 @@
# Error in setTimeout # Error in setTimeout
How do you think, does the `.catch` trigger? Explain, why? How do you think, does the `.catch` trigger? Explain your answer?
```js ```js
new Promise(function(resolve, reject) { new Promise(function(resolve, reject) {

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
# Promises chaining # Promises chaining
Let's formulate the problem mentioned in the chapter <info:callback-hell>: Let's return to the problem mentioned in the chapter <info:callbacks>:
- We have a sequence of asynchronous tasks to be done one after another. For instance, loading scripts. - We have a sequence of asynchronous tasks to be done one after another. For instance, loading scripts.
- How to code it well? - How to code it well?
@ -24,7 +24,7 @@ new Promise(function(resolve, reject) {
alert(result); // 1 alert(result); // 1
return result * 2; return result * 2;
}).then(function(result) { }).then(function(result) { // (***)
alert(result); // 2 alert(result); // 2
return result * 2; return result * 2;
@ -37,15 +37,40 @@ new Promise(function(resolve, reject) {
}); });
``` ```
Here the first promise resolves in 1 second `(*)`, then the first handler is called `(**)`, its result is passed down to the second one etc. The result is passed along the chain of handlers, so we can see a sequence of `alert` calls: `1` -> `2` -> `4`. The idea is that the result is passed through the chain of `.then` handlers.
Here the flow is:
1. The initial promise resolves in 1 second `(*)`,
2. Then the `.this` handler is called `(**)`.
3. The value that it returns is passed to the next `.this` handler `(***)`
4. ...and so on.
As the result is passed along the chain of handlers, we can see a sequence of `alert` calls: `1` -> `2` -> `4`.
![](promise-then-chain.png) ![](promise-then-chain.png)
The whole thing works, because a call to `promise.then` returns a promise, so that we can call next `.then` on it, to get a new promise and so on. The whole thing works, because a call to `promise.then` returns a promise, so that we can call next `.then` on it.
A result of a handler becomes a result of the promise returned by the corresponding `.then`. When a handler returns a value, it becomes the result of that promise, so the next `.then` is called with it.
Please note: technically we can also add many `.then` to a single promise, without any chaining, like here: To make things clear, here's the start of the chain:
```js run
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return result * 2; // (1)
}) // <-- (2)
```
The value returned by `.then` is a promise, so we can add another `.then` at `(2)`. When the value is returned in `(1)`, that promise becomes resolved, so the next handler triggers with it.
Unlike the chaining, technically we can also add many `.then` to a single promise, like this:
```js run ```js run
let promise = new Promise(function(resolve, reject) { let promise = new Promise(function(resolve, reject) {
@ -68,13 +93,13 @@ promise.then(function(result) {
}); });
``` ```
...But that's a totally different thing. All `.then` on the same promise get the same result -- the result of that promise: ...But that's a totally different thing. Here's the picture (compare it with the chaining above):
![](promise-then-many.png) ![](promise-then-many.png)
So in the code above all `alert` show the same: 1. There is no result-passing between them. All `.then` on the same promise get the same result -- the result of that promise. So in the code above all `alert` show the same: `1`. There is no result-passing between them.
In practice chaining is used far more often than adding many handlers to the same promise. In practice we rarely need multiple handlers for one promise. Chaining is used much more often.
## Returning promises ## Returning promises
@ -94,20 +119,18 @@ new Promise(function(resolve, reject) {
alert(result); // 1 alert(result); // 1
*!* *!*
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000); setTimeout(() => resolve(result * 2), 1000);
}); });
*/!* */!*
}).then(function(result) { }).then(function(result) { // (**)
alert(result); // 2 alert(result); // 2
*!*
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000); setTimeout(() => resolve(result * 2), 1000);
}); });
*/!*
}).then(function(result) { }).then(function(result) {
@ -116,15 +139,15 @@ new Promise(function(resolve, reject) {
}); });
``` ```
Here each `.then` returns `new Promise(…)`. When it settles, the result is passed on. Here the first `.then` shows `1` returns `new Promise(…)` in the line `(*)`. After one second it resolves, and the result is passed on to handler of the second `.then` in the line `(**)`, that shows `2` and so on.
So the output is again 1 -> 2 > 4, but with 1 second delay between `alert` calls. So the output is again 1 -> 2 > 4, but now with 1 second delay between `alert` calls.
That feature allows to build chains of asynchronous actions. Returning promises allows us to build chains of asynchronous actions.
## Example: loadScript ## Example: loadScript
Let's use it with `loadScript` to load scripts one by one, sequentially: Let's use this feature with `loadScript` to load scripts one by one, in sequence:
```js run ```js run
loadScript("/article/promise-chaining/one.js") loadScript("/article/promise-chaining/one.js")
@ -143,69 +166,211 @@ loadScript("/article/promise-chaining/one.js")
}); });
``` ```
Here each `loadScript` call returns a promise, and the next `.then` awaits until it resolves. So scripts are loaded one after another. Here each `loadScript` call returns a promise, and the next `.then` runs when it resolves. Then it initiates the loading of the next script. So scripts are loaded one after another.
We can add more asynchronous actions to the chain, and the code is still "flat", no signs of "pyramid of doom". 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 it is also possible to write `.then` directly after each promise, without returning them, like this:
```js run
loadScript("/article/promise-chaining/one.js").then(function(script1) {
loadScript("/article/promise-chaining/two.js").then(function(script2) {
loadScript("/article/promise-chaining/three.js").then(function(script3) {
// this function has access to variables script1, script2 and script3 (*)
one();
two();
three();
});
});
});
```
This code does the same: loads 3 scripts in sequence. But it "grows to the right". So we have the same problem as with callbacks. Use chaining (return promises from `.then`) to evade it.
Sometimes it's ok to write `.then` directly, because the nested function has access to the outer scope `(*)`, but that's an exception rather than a rule.
## Bigger example: fetch
In frontend programming promises are often used for network requests. So let's make an example of that.
We'll use the [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) method to load the information about the user from the remote server. The method is quite complex, it has many optional parameters, but the basic usage is quite simple:
```js
let promise = fetch(url);
```
Makes a network request to the `url` and returns a promise. The promise resolves with a `response` object when the remote server responds with headers, but before the full response is downloaded.
To read the full response, we should call a method `response.text()`: it returns a promise that resolves with the full response text when it's downloaded from the remote server.
The code below makes a request to `user.json` and then loads it as text from the server:
```js run
fetch('/article/promise-chaining/user.json')
// .then runs when the remote server responds
.then(function(response) {
// response.text() is the new promise that resolves when the server finishes sending data
return response.text();
})
.then(function(text) {
// ...and here's the content of the remote file
alert(text); // {"name": "iliakan", isAdmin: true}
});
```
There is also a method `response.json()` that reads the remote data and parses it as JSON. In our case that's even more convenient.
We'll also use arrow functions for brevity:
```js run
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => alert(user.name)); // iliakan
```
Now let's do something with it. For instance, we can make one more request to github, load the user profile and show the avatar:
```js run
// 1. Make a request for user.json
fetch('/article/promise-chaining/user.json')
// Load it as json
.then(response => response.json())
// Make a request to github
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// Load the response as json
.then(response => response.json())
// Show the avatar image (githubUser.avatar_url) for 3 seconds (maybe animate it)
.then(githubUser => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => img.remove(), 3000); // (*)
});
```
The code works. But there's a potential problem in it.
Look at the line `(*)`: how can we do something *after* the avatar is removed? For instance, we'd like to show a form for editing that user or something else.
To make the chain extendable, we need to return a promise that resolves when the avatar finishes showing.
Here's how:
```js run
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
*!*
.then(githubUser => new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
*/!*
// triggers after 3 seconds
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
```
Now when `setTimeout` runs the function, it calls `resolve(githubUser)`, thus passing the control to the next `.then` in the chain and passing forward the user data.
As a rule, an asynchronous action should always return a promise. That makes possible to plan actions after it. Even if we don't plan to extend the chain now, we may need it later.
Finally, we can split the code into reusable functions:
```js run
function loadJson(url) {
return fetch(url)
.then(response => response.json());
}
function loadGithubUser(name) {
return fetch(`https://api.github.com/users/${name}`)
.then(response => response.json());
}
function showAvatar(githubUser) {
return new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
});
}
loadJson('/article/promise-chaining/user.json')
.then(user => loadGithubUser(user.name))
.then(showAvatar)
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
// ...
```
## Error handling ## Error handling
Asynchronous actions may sometimes fail. For instance, `loadScript` fails if there's no such script. In that case the promise becomes rejected, so we can use `.catch` to handle it. Asynchronous actions may sometimes fail: in case of an error the corresponding promises becomes rejected. For instance, `fetch` fails if the remote server is not available. We can use `.catch` to handle errors (rejections).
Luckily, chaining is great for catching errors. When a promise rejects, the control jumps to the closest rejection handler down the chain. That's very convenient. Promise chaining is great at that aspect. When a promise rejects, the control jumps to the closest rejection handler down the chain. That's very convenient in practice.
In the example below we append `.catch` to handle all errors in the scripts loading chain: For instance, in the code below the URL is wrong (no such server) and `.catch` handles the error:
```js run ```js run
*!* *!*
loadScript("/article/promise-chaining/ERROR.js") fetch('https://no-such-server.blabla') // rejects
*/!*
.then(function(script) {
return loadScript("/article/promise-chaining/two.js");
})
.then(function(script) {
return loadScript("/article/promise-chaining/three.js");
})
.then(function(script) {
alert('done!');
})
*!*
.catch(function(error) { // (*)
alert(error.message);
});
*/!* */!*
.then(response => response.json())
.catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)
``` ```
In the code above the first `loadScript` call fails, because `ERROR.js` doesn't exist. The control jumps to the closest error handler `(*)`. Or, maybe, everything is all right with the server, but the response is not a valid JSON:
In the example below the second script fails to load. Please note that the same `.catch` handles it, just because it's the closest one down the chain:
```js run ```js run
loadScript("/article/promise-chaining/one.js") fetch('/') // fetch works fine now, the server responds successfully
.then(function(script) {
*!* *!*
return loadScript("/article/promise-chaining/ERROR.js"); .then(response => response.json()) // rejects: the page is HTML, not a valid json
*/!*
})
.then(function(script) {
return loadScript("/article/promise-chaining/three.js");
})
.then(function(script) {
alert('done!');
})
*!*
.catch(function(error) {
alert(error.message);
});
*/!* */!*
.catch(err => alert(err)) // SyntaxError: Unexpected token < in JSON at position 0
``` ```
The `.catch` works like in a `try..catch` block. We may have as many `.then` as we want, and then use a single `.catch` at the end to handle errors in all of them.
In the example below we append `.catch` to handle all errors in the avatar-loading-and-showing chain:
```js run
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
.catch(error => alert(error.message));
```
Here `.catch` doesn't trigger at all, because there are no errors. But if any of the promises above rejects, then it would execute.
## Implicit try..catch ## Implicit try..catch
The code inside the executor and handlers has something like an invisible `try..catch` around it. If an error happens, it's considered a rejection. The code of the executor and promise handlers has an "invisible `try..catch`" around it. If an error happens, it gets caught and treated as a rejection.
For instance, this code: For instance, this code:
@ -214,9 +379,7 @@ new Promise(function(resolve, reject) {
*!* *!*
throw new Error("Whoops!"); throw new Error("Whoops!");
*/!* */!*
}).catch(function(error) { }).catch(alert); // Error: Whoops!
alert(error.message); // Whoops!
});
``` ```
...Works the same way as this: ...Works the same way as this:
@ -226,14 +389,12 @@ new Promise(function(resolve, reject) {
*!* *!*
reject(new Error("Whoops!")); reject(new Error("Whoops!"));
*/!* */!*
}).catch(function(error) { }).catch(alert); // Error: Whoops!
alert(error.message); // Whoops!
});
``` ```
The `Promise` constructor automatically catches the error and treats it as a rejection. The "invisible `try..catch`" around the executor automatically catches the error and treats it as a rejection.
That works not only in the executor, but in handlers as well. If we `throw` inside `.then` handler, that means a rejected promise, so the control jumps to the nearest error handler. That's so not only in the executor, but in handlers as well. If we `throw` inside `.then` handler, that means a rejected promise, so the control jumps to the nearest error handler.
Here's an example: Here's an example:
@ -242,17 +403,12 @@ new Promise(function(resolve, reject) {
resolve("ok"); resolve("ok");
}).then(function(result) { }).then(function(result) {
*!* *!*
// .then returns a rejected promise throw new Error("Whoops!"); // rejects the promise
throw new Error("Whoops!");
*/!* */!*
}).catch(function(error) { }).catch(alert); // Error: Whoops!
// and the error is handled here
alert(error.message); // Whoops!
});
``` ```
That's so not only for `throw`, That's so not only for `throw`, but for any errors, including programming errors as well:
but for any errors, including programming errors as well:
```js run ```js run
new Promise(function(resolve, reject) { new Promise(function(resolve, reject) {
@ -261,29 +417,18 @@ new Promise(function(resolve, reject) {
*!* *!*
blabla(); // no such function blabla(); // no such function
*/!* */!*
}).catch(function(error) { }).catch(alert); // ReferenceError: blabla is not defined
alert(error.message); // blabla is not defined
});
``` ```
To summarize, a promise handler can finish in three ways: As a side effect, the final `.catch` not only catches explicit rejections, but also occasional errors in the handlers above.
1. It can return a value (or undefined if there's no `return`). Then the promise returned by `.then` becomes fulfilled, and the next handler is called with that value.
2. It can throw an error. Then the promise returned by `.then` becomes rejected and the closest rejection handler is called.
3. It can return a promise. Then JavaScript awaits its result and goes on with it.
![](promise-handler-variants.png)
## Rethrowing ## Rethrowing
As we already noticed, `.catch` behaves like `try..catch`. We may have as many `.then` as we want, and then use a single `.catch` at the end to handle errors in all of them. As we already noticed, `.catch` behaves like `try..catch`. We may have as many `.then` as we want, and then use a single `.catch` at the end to handle errors in all of them.
In a regular `try..catch` we can analyze the error and maybe rethrow it if can't handle. The same thing is possible for promises. In a regular `try..catch` we can analyze the error and maybe rethrow it if can't handle. The same thing is possible for promises. If we `throw` inside `.catch`, then the control goes to the next closest error handler. And if we handle the error and finish normally, then it continues to the closest successful `.then` handler.
If we `throw` inside `.catch`, then the control goes to the next closest error handler. And if we finish normally, then it continues to the closest successful `.then` handler.
In the example below the error is fully handled, and the execution continues normally:
In the example below the `.catch` successfully handles the error:
```js run ```js run
// the execution: catch -> then // the execution: catch -> then
new Promise(function(resolve, reject) { new Promise(function(resolve, reject) {
@ -292,18 +437,14 @@ new Promise(function(resolve, reject) {
}).catch(function(error) { }).catch(function(error) {
alert("Handled it!"); alert("The error is handled, continue normally");
*!*
return "result"; // return, the execution goes the "normal way"
*/!*
*!* }).then(() => alert("Next successful handler runs"));
}).then(alert); // result shown
*/!*
``` ```
...And here's an example of "rethrowing": Here the `.catch` block finishes normally. So the next successful handler is called. Or it could return something, that would be the same.
...And here the `.catch` block analyzes the error and throws it again:
```js run ```js run
// the execution: catch -> catch -> then // the execution: catch -> catch -> then
@ -313,21 +454,27 @@ new Promise(function(resolve, reject) {
}).catch(function(error) { // (*) }).catch(function(error) { // (*)
alert("Can't handle!"); if (error instanceof URIError) {
// handle it
} else {
alert("Can't handle such error");
*!* *!*
throw error; // throwing this or another error jumps to the next catch throw error; // throwing this or another error jumps to the next catch
*/!* */!*
}
}).catch(error => { }).then(function() {
/* never runs here */
}).catch(error => { // (**)
alert("Trying to handle again..."); alert(`The unknown error has occured: ${error}`);
// don't return anything => execution goes the normal way // don't return anything => execution goes the normal way
}).then(alert); // undefined });
``` ```
The handler `(*)` catches the error. In real project it would try to handle it somehow, but here it just throws it again. So the execution jumps to the next `.catch` down the chain. The handler `(*)` catches the error and just can't handle it, because it's not `URIError`, so it throws it again. Then the execution jumps to the next `.catch` down the chain `(**)`.
## Unhandled rejections ## Unhandled rejections
@ -357,7 +504,9 @@ new Promise(function() {
Technically, when an error happens, the promise state becomes "rejected", and the execution should jump to the closest rejection handler. But there is no such handler in the examples above. Technically, when an error happens, the promise state becomes "rejected", and the execution should jump to the closest rejection handler. But there is no such handler in the examples above.
Usually that means that the code is bad. Most JavaScript engines track such situations and generate a global error. In the browser we can catch it using `window.addEventListener('unhandledrejection')` (as specified in the [HTML standard](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections)): Usually that means that the code is bad. Indeed, how come that there's no error handling?
Most JavaScript engines track such situations and generate a global error in that case. In the browser we can catch it using `window.addEventListener('unhandledrejection')` as specified in the [HTML standard](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections):
```js run ```js run
@ -378,38 +527,28 @@ new Promise(function() {
}); });
``` ```
In non-browser environments there are also a similar events, so we can always track unhandled errors in promises. Now if an error has occured, and there's no `.catch`, the event `unhandledrejection` triggers, and our handler can do something with the exception. Once again, such situation is usually a programming error.
In non-browser environments like Node.JS there are other similar ways to track unhandled errors.
## Summary ## Summary
- A call to `.then/catch` returns a promise, so we can chain them. To summarize, `.then/catch(handler)` returns a new promise that changes depending on what handler does:
- There are 3 possible outcomes of a handler:
1. Return normally -- the result is passed to the closest successful handler down the chain.
2. Throw an error -- it is passed to the closest rejection handler down the chain.
3. Return a promise -- the chain waits till it settles, and then its result is used.
That allows to put a "queue" of actions into the chain. 1. If it returns a value or finishes without a `return` (same as `return undefined`), then the new promise becomes resolved, and the closest resolve handler (the first argument of `.then`) is called with that value.
2. If it throws an error, then the new promise becomes rejected, and the closest rejection handler (second argument of `.then` or `.catch`) is called with it.
3. If it returns a promise, then JavaScript waits until it settles and then acts on its outcome the same way.
Here's a more complex example. The function `showMessage` loads a message and shows it: The picture of how the promise returned by `.then/catch` changes:
```js run ![](promise-handler-variants.png)
function showMessage() {
return new Promise(function(resolve, reject) {
alert('loading...');
return loadScript('/article/promise-chaining/getMessage.js');
}).then(function(script) {
let message = getMessage(); // getMessage function comes from the script
return animateMessage(message);
}).catch(function(err) { /*...*/ });
}
function animateMessage(message) { The smaller picture of how handlers are called:
return new Promise(function(resolve, reject) {
// should be asynchronous animation
alert(message);
resolve();
});
}
showMessage(); ![](promise-handler-variants-2.png)
```
In the examples of error handling above the `.catch` was always the last in the chain. In practice though, not every promise chain has a `.catch`. Just like regular code is not always wrapped in `try..catch`.
We should place `.catch` exactly in the places where we want to handle errors and know how to handle them.
For errors that are outside of that scope we should have the `unhandledrejection` event handler. Such unknown errors are usually unrecoverable, so all we should do is to inform the user and probably report to our server about the incident.

View file

@ -11,3 +11,12 @@ function loadScript(src) {
}); });
} }
</script> </script>
<style>
.promise-avatar-example {
border-radius: 50%;
position: fixed;
left: 10px;
top: 10px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Before After
Before After

View file

@ -1,4 +1,42 @@
# Promises API # Promise API
There are helpful static methods in the `Promise` class. There are only 4 of them, so we'll quickly cover them here.
## Promise.all
The method to run many promises in parallel and wait till all of them are ready.
The syntax is:
```js
let promise = Promise.all(iterable);
```
It takes an `iterable` object with promises, for instance an array and returns a new promise that resolves with the array of their results when all of them are settled, or rejects with the first encountered error if any.
For instance:
```js run
// loads 3 scripts in parallel and returns an array of them
Promise.all([
loadScript('/article/promise-api/one.js'),
loadScript('/article/promise-api/two.js'),
loadScript('/article/promise-api/three.js')
]).then(scripts => {
alert(`scripts loaded: ${scripts}`);
});
```
- the returned `promise` awaits for al
In the previous chapter we saw how to run things sequentially. Promises also
- Promise.all
- Promise.race
- Promise.reject
- Promise.resolve
Let's meet more functions and methods for promises. Let's meet more functions and methods for promises.

View file

@ -0,0 +1,13 @@
<script>
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);
});
}
</script>

View file

@ -0,0 +1,3 @@
function one() {
alert(1);
}

View file

@ -0,0 +1,3 @@
function three() {
alert(3);
}

View file

@ -0,0 +1,3 @@
function two() {
alert(2);
}

View file

@ -2,4 +2,4 @@ development: true
--- ---
# Promises, async/await # The art of async programming: promises, async/await

Binary file not shown.