This commit is contained in:
Ilya Kantor 2017-05-01 11:44:56 +03:00
parent a68022a8d6
commit 04b2fcfba2
17 changed files with 304 additions and 137 deletions

View file

@ -174,7 +174,7 @@ let user = {
set fullName(value) { set fullName(value) {
[this.name, this.surname] = value.split(" "); [this.name, this.surname] = value.split(" ");
} },
get fullName() { get fullName() {
return `${this.name} ${this.surname}`; return `${this.name} ${this.surname}`;

View file

@ -139,7 +139,7 @@ Please note:
1. `getAttribute('About')` -- the first letter is uppercase here, and in HTML it's all lowercase. But that doesn't matter: attribute names are case-insensitive. 1. `getAttribute('About')` -- the first letter is uppercase here, and in HTML it's all lowercase. But that doesn't matter: attribute names are case-insensitive.
2. We can assign anything to an attribute, but that becomes a string. So here we have `"123"` as the value. 2. We can assign anything to an attribute, but that becomes a string. So here we have `"123"` as the value.
3. All attributes including ones that we set are seen in `innerHTML`. 3. All attributes including ones that we set are visible in `outerHTML`.
4. The `attributes` collection is iterable and has all attributes with `name` and `value`. 4. The `attributes` collection is iterable and has all attributes with `name` and `value`.
## Property-attribute synchronization ## Property-attribute synchronization

View file

@ -67,7 +67,7 @@ There are two differences between them.
| | `async` | `defer` | | | `async` | `defer` |
|---------|---------|---------| |---------|---------|---------|
| Order | Scripts with `async` execute *in the load-first order*. Their document order doesn't matter -- which loads first runs first. | Scripts with `defer` always execute *in the document order* (as they go in the document). | | Order | Scripts with `async` execute *in the load-first order*. Their document order doesn't matter -- which loads first runs first. | Scripts with `defer` always execute *in the document order* (as they go in the document). |
| `DOMContentLoaded` | Scripts with `async` may load and execute while the document has not yet been fully downloaded. | Scripts with `defer` execute after the document is loaded and parsed (they wait if needed), right before `DOMContentLoaded`. | | `DOMContentLoaded` | Scripts with `async` may load and execute while the document has not yet been fully downloaded. That happens if scripts are small or cached, and the document is long enough. | Scripts with `defer` execute after the document is loaded and parsed (they wait if needed), right before `DOMContentLoaded`. |
So `async` is used for totally independent scripts. So `async` is used for totally independent scripts.

View file

@ -186,7 +186,7 @@ Function `animate` accepts 3 parameters that essentially describes the animation
Let's animate the element `width` from `0` to `100%` using our function. Let's animate the element `width` from `0` to `100%` using our function.
Click for the demo: Click on the element for the demo:
[codetabs height=60 src="width"] [codetabs height=60 src="width"]
@ -410,9 +410,7 @@ As we can see, the graph of the first half of the animation is the scaled down `
## More interesting "draw" ## More interesting "draw"
Instead of moving the element we can do something else. All we need is to write the write `draw` Instead of moving the element we can do something else. All we need is to write the write the proper `draw`.
### Typing in the text
Here's the animated "bouncing" text typing: Here's the animated "bouncing" text typing:
@ -420,9 +418,11 @@ Here's the animated "bouncing" text typing:
## Summary ## Summary
JavaScript animation is implemented with the help of `requestAnimationFrame`. JavaScript animation should be implemented via `requestAnimationFrame`. That built-in method allows to setup a callback function to run when the browser will be preparing a repaint. Usually that's very soon, but the exact time depends on the browser.
The helper `animate` function: When a page is in the background, there are no repaints at all, so the callback won't run: the animation will be suspended and won't consume resources. That's great.
Here's the helper `animate` function to setup most animations:
```js ```js
function animate({timing, draw, duration}) { function animate({timing, draw, duration}) {

View file

@ -10,7 +10,7 @@
<body> <body>
<textarea id="textExample" rows="4" cols="60">He took his vorpal sword in hand: <textarea id="textExample" rows="5" cols="60">He took his vorpal sword in hand:
Long time the manxome foe he sought— Long time the manxome foe he sought—
So rested he by the Tumtum tree, So rested he by the Tumtum tree,
And stood awhile in thought. And stood awhile in thought.

View file

@ -30,32 +30,35 @@ A *synchronous* action suspends the execution until it's completed. For instance
```js ```js
let age = prompt("How old are you", 20); let age = prompt("How old are you", 20);
// the execution of the code below awaits for the prompt to finish // 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: 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 does not wait for the script loading to finish, // the execution of the code below *does not* wait for the script loading to finish,
// it just goes on // it just continues
``` ```
As of now, `loadScript` provides no way to track the load completion. The script loads and eventually runs. 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.
Let's add a `callback` function as a second argument to `loadScript`, that should execute at when the script is loaded. Let's add a `callback` function as a second argument to `loadScript` that should execute when the script loads:
```js ```js
function loadScript(src, callback) { function loadScript(src, callback) {
let script = document.createElement('script'); let script = document.createElement('script');
script.src = src; script.src = src;
*!*
script.onload = () => callback(script); script.onload = () => callback(script);
*/!*
document.head.append(script); document.head.append(script);
} }
``` ```
Now we're able to load a script and run our code that can use new functions from it, like here: Now we're able to load a script and then run our code that can use new functions from it, like here:
```js run ```js run
function loadScript(src, callback) { function loadScript(src, callback) {
@ -73,7 +76,9 @@ loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', f
*/!* */!*
``` ```
That's called a "callback style" of asynchronous programming. A function that does something asynchronously provides a callback argument where we put the code 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.
## Callback in callback ## Callback in callback
@ -151,10 +156,10 @@ loadScript('/my/script.js', function(error, script) {
``` ```
The convention is: The convention is:
1. The first argument of `callback` is reserved for an error if it occurs. 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. 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. 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.
## Pyramid of doom ## Pyramid of doom
@ -203,6 +208,8 @@ That's sometimes called "callback hell" or "pyramid of doom".
The pyramid grows to the right with every asynchronous action. Soon it spirales out of control. The pyramid grows to the right with every asynchronous action. Soon it spirales out of control.
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: In simple cases we can evade the problem by making every action a standalone function, like this:
```js ```js
@ -235,6 +242,10 @@ function step3(error, script) {
}; };
``` ```
See? 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. The functions `step*` have no use, they are only created to evade the "pyramid of doom". 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.
Luckily, there are other ways to evade such pyramids. Most modern code makes use of "promises", we'll study them in the next chapter. Also the functions `step*` have no use, they are only created to evade the "pyramid of doom".
So 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.

View file

@ -1,63 +1,77 @@
# Promise # Promise
A promise is an object of the built-in `Promise` class. Imagine that you're a top singer, and fans ask for your next upcoming single day and night.
The promise object has two main internal properties: `state` and the `result`. 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.
The idea is that we have one place in the code that creates a promise object and manages its state and result ("producing" code), and other places that await for the result and then make use of it ("consuming" code). Everyone is happy: you, because 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:
1. A "producing code" does something that 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".
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.
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 constructor syntax is: The constructor syntax is:
```js ```js
let promise = new Promise(function(resolve, reject) { let promise = new Promise(function(resolve, reject) {
// executor // producing code
}); });
``` ```
The function passed to `new Promise` is called *executor*. When the promise is created, it is called automatically. 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 purpose of that function is to do a job and then change the state of the promise by calling one of:
The `promise` object has internal properties:
- `state` -- initially is "pending", then changes to "fulfilled" or "rejected",
- `result` -- an arbitrary value, initially `undefined`.
When the executor finishes the job, it should change the state of the promise by calling one of:
- `resolve(value)` -- to indicate that the job finished successfully: sets `state` to `"fulfilled"`, and `result` to `value`. - `resolve(value)` -- to indicate that the job finished successfully: sets `state` to `"fulfilled"`, and `result` to `value`.
- `reject(error)` -- to indicate that an error occured: sets `state` to `"rejected"` and `result` to `error`. - `reject(error)` -- to indicate that an error occured: sets `state` to `"rejected"` and `result` to `error`.
![](promise-resolve-reject.png) ![](promise-resolve-reject.png)
Let's see examples. 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`.
First, a simplest executor, just to see how it works: Here's a simple executor, just to see it in action:
```js run ```js run
let promise = new Promise(function(resolve, reject) { let promise = new Promise(function(resolve, reject) {
// the function is executed automatically when the promise is constructed
alert(resolve); // function () { [native code] } alert(resolve); // function () { [native code] }
alert(reject); // function () { [native code] } alert(reject); // function () { [native code] }
}); });
``` ```
We can see two things here: We can see two things by running the code above:
1. The executor is called immediately by `new Promise`. 1. The executor is called automatically and immediately (by `new Promise`).
2. Both functions `resolve` and `reject` are provided by JavaScript itself. We don't need to create them. Their string representation may vary between JavaScript engines. 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 wait 1 second and then resolve the promise: Now let's make the executor that thinks for 1 second and then produces a result:
```js ```js
let promise = new Promise(function(*!*resolve*/!*, reject) { let promise = new Promise(function(resolve, reject) {
// this function is executed automatically when the promise is constructed
// after 1 second signal that the job is done with the result "done!" // after 1 second signal that the job is done with the result "done!"
setTimeout(() => resolve("done!"), 1000); setTimeout(() => *!*resolve("done!")*/!*, 1000);
}); });
``` ```
![](promise-resolve-1.png) ![](promise-resolve-1.png)
Here we wait 1 second and then reject the promise with an error: The next example rejects the promise with an error:
```js ```js
let promise = new Promise(function(resolve, *!*reject*/!*) { let promise = new Promise(function(resolve, reject) {
// after 1 second signal that the job is finished with an error // after 1 second signal that the job is finished with an error
setTimeout(() => reject(new Error("Whoops!")), 1000); setTimeout(() => *!*reject(new Error("Whoops!"))*/!*, 1000);
}); });
``` ```
@ -65,9 +79,10 @@ let promise = new Promise(function(resolve, *!*reject*/!*) {
The promise that was either resolved or rejected is called "settled". The promise that was either resolved or rejected is called "settled".
````smart header="There can be only one result or an error"
We must call only one `resolve` or `reject`. And that's final. We must call only one `resolve` or `reject`. And that's final.
All calls of `resolve` and `reject` after the first one are ignored: All further calls of `resolve` and `reject` are ignored:
```js ```js
let promise = new Promise(function(resolve, reject) { let promise = new Promise(function(resolve, reject) {
@ -78,12 +93,13 @@ let promise = new Promise(function(resolve, reject) {
}); });
``` ```
The idea is that a job may have only one result or an error. 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.
There are other data structures for jobs that have many results, for instance streams and queues. But they are not supproted by JavaScript language core and lack certain features that promises provide. 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.
````
```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). 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.
``` ```
````smart header="Resolve/reject can be immediate" ````smart header="Resolve/reject can be immediate"
@ -95,19 +111,21 @@ let promise = new Promise(function(resolve, reject) {
}); });
``` ```
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.
```` ````
## Consumers: ".then" and ".catch" ## Consumers: ".then" and ".catch"
A promise object allows to add "handlers" to run when it becomes settled. A promise object allows to add "consumers" -- handler functions to run when it resolves/rejects.
The syntax is: The syntax is:
```js ```js
promise.then( promise.then(
function(result) { /* process a sucessful result */ }, function(result) { /* handle a sucessful result */ },
function(error) { /* process an error */ } function(error) { /* handle an error */ }
); );
``` ```
@ -155,7 +173,7 @@ promise.then(alert); // shows "done!" after 1 second
*/!* */!*
``` ```
If we're interested only in errors, then we can use `.then(null, function)` or a special shorthand: `.catch` If we're interested only in errors, then we can use `.then(null, function)` or an "alias" to it: `.catch(function)`
```js run ```js run
@ -169,7 +187,22 @@ promise.catch(alert); // shows "Error: Whoops!" after 1 second
*/!* */!*
``` ```
Now let's see more practical examples to explore the benefits over the callback-based approach.
````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:
```js run
let promise = new Promise(resolve => resolve("done!")); // immediately resolve
promise.then(alert); // done! (without 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.
````
Now let's see more practical examples to see how promises can help us to write asynchronous code.
## Example: loadScript ## Example: loadScript
@ -205,14 +238,15 @@ function loadScript(src) {
document.head.append(script); document.head.append(script);
}); });
} }
```
*!* Usage:
// Usage:
*/!* ```js run
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js"); let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js");
promise.then( promise.then(
script => alert(`${script.src} is loaded!`), script => alert(`${script.src} is loaded!`),
error => alert(`Error: ${error.message}`); error => alert(`Error: ${error.message}`)
); );
promise.then(script => alert('One more handler to do something else!')); promise.then(script => alert('One more handler to do something else!'));
@ -227,64 +261,4 @@ We can immediately see few benefits over the callback-based syntax:
+ We can call `.then` as many times as we want. + We can call `.then` as many times as we want.
``` ```
So promises give us better code flow and flexibility. But there's more. We'll see that in the next chapters. So promises give us better code flow and flexibility. But there's more. We'll see that in the next chapters.
````smart header="On settled promises `then` runs immediately"
We can add `.then/catch` at any time.
If the promise has not settled yet, then it will wait for the result. Otherwise, if the promise has already settled, then `promise.then/catch` handlers run immediately:
```js run
let promise = new Promise(resolve => resolve("done!")); // immediately resolve
promise.then(alert); // done! (without delay)
```
To be precise -- handlers are always asynchronous, but if the promise is settled they run as soon as possible, similar to `setTimeout(...,0)`.
For instance, here we'll first see "code end" `(1)`, and then "done!" `(2)`:
```js run
let promise = new Promise(resolve => resolve("done!"));
// alert runs as soon as possible, but asynchronously,
// like if it were wrapped in setTimeout(..., 0);
promise.then(alert); // (2)
alert('code end'); // (1)
```
````
````smart header="Functions resolve/reject accept at most one argument"
Functions `resolve/reject` accept only one argument.
We can call them without any arguments too, that's the same as passing `undefined`:
```js run
let promise = new Promise(resolve => resolve());
promise.then(alert); // undefined
```
...But if we pass many arguments: `resolve(1, 2, 3)`, then all arguments after the first one are ignored.
The idea is that a promise may have only one result (or an error). Use objects and destructuring if you need to pass many values, like this:
```js run
let promise = new Promise(function(resolve, reject) {
let name = "John";
let age = 25;
resolve({name, age}); // "pack" the values in an object
});
// destructuring
promise.then(function({name, age}) {
// here we have name and age variables as if the promise had two results
alert(`${name} ${age}`); // John 25
})
```
````

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

@ -6,11 +6,13 @@ Let's formulate the problem mentioned in the chapter <info:callback-hell>:
- 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?
Promises allow a couple of recipes to do that. Promises provide a couple of recipes to do that.
[cut] [cut]
In this chapter we cover promise chaining. It looks like this: In this chapter we cover promise chaining.
It looks like this:
```js run ```js run
new Promise(function(resolve, reject) { new Promise(function(resolve, reject) {
@ -35,15 +37,26 @@ new Promise(function(resolve, reject) {
}); });
``` ```
As we can see: The result is passed along the chain of `.then`, so we can see a sequence of `alert` calls: `1` -> `2` -> `4`.
- A call to `promise.then` returns a promise, that we can use for the next `.then` (chaining).
- A value returned by `.then` handler becomes a result in the next `.then`.
![](promise-then-chain.png) ![](promise-then-chain.png)
So in the example above we have a sequence of results: `1` -> `2` -> `4`. A call to `promise.then` returns a promise, so that we can call next `.then` on it. Here's a shorter code to illustrate that:
Please note the technically we can also add many `.then` to a single promise, without any chaining, like here: ```js run
let p = new Promise((resolve, reject) => { /*...*/ });
function handler() { /*...*/ }
let p2 = p.then(handler);
*!*
alert(p2); // [object Promise]
*/!*
```
When the `handler` is called, the value that it returns becomes the result of that promise `p2`. So if we add `p2.then`, the next handler receives that result, and so on.
Please note: technically we can also add many `.then` to a single promise, without any chaining, like here:
```js run ```js run
let promise = new Promise(function(resolve, reject) { let promise = new Promise(function(resolve, reject) {
@ -76,7 +89,9 @@ In practice chaining is used far more often than adding many handlers to the sam
## Returning promises ## Returning promises
Normally, a 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 given to the next `.then`. Normally, a value returned by a handler is passed to the next `.then` "as is". But there's an exception.
If the returned value is a promise, then further execution is suspended until it settles. And then the result of that promise is given to the next `.then` handler.
For instance: For instance:
@ -116,27 +131,31 @@ Here each `.then` returns `new Promise(…)`. JavaScript awaits for it to settle
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 with 1 second delay between `alert` calls.
That feature allows to insert asynchronous actions into the chain.
````smart header="Thenables" ````smart header="Thenables"
To be precise, any object that has a method `.then` is treated as a promise here. So that we could use a custom "promise-compatible" object in the chain. Such objects are called "thenable". To be precise, any object that has a method `.then` is treated as a promise here. So that we could use a custom "promise-compatible" object in the chain. Such objects are called "thenable".
Here's an example: Here's an example:
```js run ```js run
let thenable = { class Thenable {
constructor(result, delay) {
this.result = result;
}
then(resolve, reject) { then(resolve, reject) {
setTimeout(() => resolve(result * 2), 1000); setTimeout(() => resolve(this.result * 2), delay);
} }
}; };
new Promise(resolve => resolve(1)) new Promise(resolve => resolve(1))
.then(result => { .then(result => {
return thenable; return new Thenable(result, 1000);
}) })
.then(alert); // shows 2 after 1000ms .then(alert); // shows 2 after 1000ms
``` ```
In practice we rarely need thenables, because if we want to make our own promise-compatible objects, then we can inherit from the native `Promise` class.
But that's still good, because gives us additional flexibility. We don't *have* to inherit from `Promise`. In particular, that allows to use a custom implementations of promises coming from 3rd-party libraries along with native promises. We also can inherit from the native `Promise` class, but technically we don't have to. That allows to use custom implementations of promises from 3rd-party libraries along with native promises.
```` ````
## Example: loadScript ## Example: loadScript
@ -298,10 +317,9 @@ As we already noticed, `.catch` behaves like `try..catch`. We may have as many `
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.
An error handler has the same 3 outcomes as described above. So if we `throw` inside `.catch`, then the control goes to the next closest error handler. And if we finish normally, then it continues to TODO 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.
TODO TODO In the example below the error is fully handled, and the execution continues normally:
Here is an example of the first behavior (the error is handled):
```js run ```js run
// the execution: catch -> then // the execution: catch -> then
@ -316,7 +334,7 @@ new Promise(function(resolve, reject) {
return "result"; // return, the execution goes the "normal way" return "result"; // return, the execution goes the "normal way"
*/!* */!*
*!* *!
}).then(alert); // result shown }).then(alert); // result shown
*/!* */!*
``` ```
@ -371,18 +389,16 @@ 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 none. 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. 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)):
```js run ```js run
// open in a new window to see in action
window.addEventListener('unhandledrejection', function(event) { window.addEventListener('unhandledrejection', function(event) {
// the event object has two special properties: // the event object has two special properties:
alert(event.promise); // the promise that generated the error alert(event.promise); // [object Promise] - the promise that generated the error
alert(event.reason); // the error itself (Whoops!) alert(event.reason); // Error: Whoops! - the unhandled error object
}); });
new Promise(function() { new Promise(function() {
@ -396,4 +412,38 @@ new Promise(function() {
}); });
``` ```
In non-browser environments there's also a similar event, so we can always track unhandled errors in promises. In non-browser environments there are also a similar events, so we can always track unhandled errors in promises.
## Summary
- A call to `.then/catch` returns a promise, so we can chain them.
- 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.
Here's a more complex example. The function `showMessage` loads a message and shows it:
```js run
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) {
return new Promise(function(resolve, reject) {
// should be asynchronous animation
alert(message);
resolve();
});
}
showMessage();
```

View file

@ -0,0 +1,3 @@
function getMessage() {
return "Hello, world!";
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

View file

@ -0,0 +1,60 @@
````smart header="Handlers are always asynchronous, like `setTimeout(...,0)`"
Handlers assigned by `.then/catch` are always asynchronous. But if the promise is settled they run as soon as possible, similar to `setTimeout(...,0)`.
Here's the example to demonstrate that:
```js run
// the promise is immediately resolved with "done!"
let promise = new Promise(resolve => resolve("done!"));
// alert runs asynchronously, like if it were wrapped in setTimeout(..., 0);
promise.then(alert); // (2)
alert('code end'); // (1)
```
We'll first see "code end" `(1)`, and then "done!" `(2)`.
Like if we had this instead of `(2)`:
```js
promise.then(result => setTimeout(() => alert(result), 0));
```
The inner mechanics is like this:
- Promise handlers form a queue.
- When the current code is complete, the queue shifts and the next handler is executed.
````
````smart header="Functions resolve/reject accept at most one argument"
Functions `resolve/reject` accept only one argument.
We can call them without any arguments too, that's the same as passing `undefined`:
```js run
let promise = new Promise(resolve => resolve());
promise.then(alert); // undefined
```
...But if we pass many arguments: `resolve(1, 2, 3)`, then all arguments after the first one are ignored.
The idea is that a promise may have only one result (or an error). Use objects and destructuring if you need to pass many values, like this:
```js run
let promise = new Promise(function(resolve, reject) {
let name = "John";
let age = 25;
resolve({name, age}); // "pack" the values in an object
});
// destructuring
promise.then(function({name, age}) {
// here we have name and age variables as if the promise had two results
alert(`${name} ${age}`); // John 25
})
```
````

37
archive/promise/fetch.js Normal file
View file

@ -0,0 +1,37 @@
## Example: fetch
## Example: fetch
Promises are also returned by some built-in browser methods.
For instance, [fetch](https://fetch.spec.whatwg.org/#fetch-method) allows to load arbitrary content over the network. We can use it to send information to the server and load data from it.
The syntax is:
```js
let promise = fetch(url[, options]);
```
Here:
- `url` is the URL to load,
- `options` are various request options.
There are many options, full information about them is in the [spec](https://fetch.spec.whatwg.org/#requestinit), here we won't need them.
The promise returned by `fetch` resolves with the `response` object when the server responds.
The most important properties of `response` are:
- `status` -- HTTP-code: 200 for OK, 404 for "Page not found" etc.
- `headers` -- HTTP headers.
-
The important thing
```js run
fetch('/article/promise-chaining/1.html?speed=1')
.then(console.log);
```

View file

@ -0,0 +1,19 @@
'use strict';
new Promise()
.then(result => {
return {
then(resolve, reject) {
setTimeout(() => resolve("two"), 1000);
}
};
})
.then(result => {
console.log("HERE", result);
return result;
})
.then(console.log)
.catch(err => console.error("ERROR", err));
// https://tc39.github.io/ecma262/#sec-promise-resolve-functions

Binary file not shown.