This commit is contained in:
Ilya Kantor 2019-08-07 11:44:39 +03:00
parent 78fbf9548d
commit e80667391f
7 changed files with 57 additions and 60 deletions

View file

@ -25,15 +25,16 @@ loadScript('/my/script.js');
The function is called "asynchronously," because the action (script loading) finishes not now, but later.
The call initiates the script loading, then the execution continues. While the script is loading, the code below may finish executing, and if the loading takes time, other scripts may run meanwhile too.
If there's a code below `loadScript(…)`, it doesn't wait until the loading finishes.
```js
loadScript('/my/script.js');
// the code below loadScript doesn't wait for the script loading to finish
// the code below loadScript
// doesn't wait for the script loading to finish
// ...
```
Now let's say we want to use the new script when it loads. It probably declares new functions, so we'd like to run them.
We'd like to use the new script as soon as it loads. It declares new functions, and we want to run them.
But if we do that immediately after the `loadScript(…)` call, that wouldn't work:
@ -45,7 +46,7 @@ newFunction(); // no such function!
*/!*
```
Naturally, the browser probably didn't have time to load the script. So the immediate call to the new function fails. 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 it happens, to use new functions and variables from that script.
Naturally, the browser probably didn't have 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 it 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:

View file

@ -2,7 +2,7 @@
Imagine that you're a top singer, and fans ask day and night for your upcoming single.
To get some relief, you promise to send it to them when it's published. You give your fans a list to which they can subscribe for updates. They can fill in their email addresses, so that when the song becomes available, all subscribed parties instantly receive it. And even if something goes very wrong, say, if plans to publish the song are cancelled, they will still be notified.
To get some relief, you promise to send it to them when it's published. You give your fans a list. They can fill in their email addresses, so that when the song becomes available, all subscribed parties instantly receive it. And even if something goes very wrong, say, fire in the studio, so that you can't publish the song, they will still be notified.
Everyone is happy, because the people don't crowd you anymore, and fans, because they won't miss the single.
@ -22,27 +22,29 @@ let promise = new Promise(function(resolve, reject) {
});
```
The function passed to `new Promise` is called the *executor*. When the promise is created, this executor function runs automatically. It contains the producing code, that should eventually produce a result. In terms of the analogy above: the executor is the "singer".
The function passed to `new Promise` is called the *executor*. When the promise is created, it runs automatically. It contains the producing code, that should eventually produce a result. In terms of the analogy above: the executor is the "singer".
The resulting `promise` object has internal properties:
Its arguments `resolve` and `reject` are callbacks provided by JavaScript itself. Our code is only inside executor.
- `state` — initially "pending", then changes to either "fulfilled" or "rejected",
- `result` — an arbitrary value, initially `undefined`.
When the executor obtains the result, be it soon or late - doesn't matter, it should call one of these callbacks:
When the executor finishes the job, it should call one of the functions that it gets as arguments:
- `resolve(value)` — if the job finished successfully, with result `value`.
- `reject(error)` — if an error occurred, `error` is the error object.
- `resolve(value)` — to indicate that the job finished successfully:
- sets `state` to `"fulfilled"`,
- sets `result` to `value`.
- `reject(error)` — to indicate that an error occurred:
- sets `state` to `"rejected"`,
- sets `result` to `error`.
So to summarize: the executor runs automatically, it should do a job and then call either `resolve` or `reject`.
The `promise` object returned by `new Promise` constructor has internal properties:
- `state` — initially `"pending"`, then changes to either `"fulfilled"` when `resolve` is called or `"rejected"` when `reject` is called.
- `result` — initially `undefined`, then changes to `value` when `resolve(value)` called or `error` when `reject(error)` is called.
So the executor moves `promise` to one of these states:
![](promise-resolve-reject.svg)
Later we'll see how these changes become known to "fans".
Later we'll see how "fans" can subscribe to these changes.
Here's an example of a Promise constructor and a simple executor function with its "producing code" (the `setTimeout`):
Here's an example of a Promise constructor and a simple executor function with delayed "producing code" (via `setTimeout`):
```js run
let promise = new Promise(function(resolve, reject) {
@ -86,7 +88,9 @@ All further calls of `resolve` and `reject` are ignored:
```js
let promise = new Promise(function(resolve, reject) {
*!*
resolve("done");
*/!*
reject(new Error("…")); // ignored
setTimeout(() => resolve("…")); // ignored
@ -99,7 +103,7 @@ Also, `resolve`/`reject` expect only one argument (or none) and will ignore addi
````
```smart header="Reject with `Error` objects"
In case something goes wrong, we can call `reject` with any type of argument (just like `resolve`). But it is recommended to use `Error` objects (or objects that inherit from `Error`). The reasoning for that will soon become apparent.
In case something goes wrong, we must call `reject`. That can be done with any type of argument (just like `resolve`). But it is recommended to use `Error` objects (or objects that inherit from `Error`). The reasoning for that will soon become apparent.
```
````smart header="Immediately calling `resolve`/`reject`"
@ -112,13 +116,13 @@ let promise = new Promise(function(resolve, reject) {
});
```
For instance, this might happen when we start to do a job but then see that everything has already been completed and cached.
For instance, this might happen when we start to do a job but then see that everything has already been completed and cached.
That's fine. We immediately have a resolved promise.
````
```smart header="The `state` and `result` are internal"
The properties `state` and `result` of the Promise object are internal. We can't directly access them from our "consuming code". We can use the methods `.then`/`.catch`/`.finally` for that. They are described below.
The properties `state` and `result` of the Promise object are internal. We can't directly access them. We can use the methods `.then`/`.catch`/`.finally` for that. They are described below.
```
## Consumers: then, catch, finally
@ -138,15 +142,9 @@ promise.then(
);
```
The first argument of `.then` is a function that:
The first argument of `.then` is a function that runs when the promise is resolved, and receives the result.
1. runs when the promise is resolved, and
2. receives the result.
The second argument of `.then` is a function that:
1. runs when the promise is rejected, and
2. receives the error.
The second argument of `.then` is a function that runs when the promise is rejected, and receives the error.
For instance, here's a reaction to a successfully resolved promise:
@ -216,7 +214,7 @@ The call `.catch(f)` is a complete analog of `.then(null, f)`, it's just a short
Just like there's a `finally` clause in a regular `try {...} catch {...}`, there's `finally` in promises.
The call `.finally(f)` is similar to `.then(f, f)` in the sense that it always runs when the promise is settled: be it resolve or reject.
The call `.finally(f)` is similar to `.then(f, f)` in the sense that `f` always runs when the promise is settled: be it resolve or reject.
`finally` is a good handler for performing cleanup, e.g. stopping our loading indicators, as they are not needed anymore, no matter what the outcome is.
@ -264,7 +262,7 @@ It's not exactly an alias of `then(f,f)` though. There are several important dif
3. Last, but not least, `.finally(f)` is a more convenient syntax than `.then(f, f)`: no need to duplicate the function `f`.
````smart header="On settled promises handlers runs immediately"
If a promise is pending, `.then/catch/finally` handlers wait for the result. Otherwise, if a promise has already settled, they execute immediately:
If a promise is pending, `.then/catch/finally` handlers wait for it. Otherwise, if a promise has already settled, they execute immediately:
```js run
// an immediately resolved promise
@ -272,8 +270,6 @@ let promise = new Promise(resolve => resolve("done!"));
promise.then(alert); // done! (shows up right now)
```
The good thing is: a `.then` handler is guaranteed to run whether the promise takes time or settles it immediately.
````
Next, let's see more practical examples of how promises can help us to write asynchronous code.
@ -324,7 +320,7 @@ promise.then(
error => alert(`Error: ${error.message}`)
);
promise.then(script => alert('One more handler to do something else!'));
promise.then(script => alert('Another handler...'));
```
We can immediately see a few benefits over the callback-based pattern:

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="512px" height="106px" viewBox="0 0 512 106" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="535px" height="106px" viewBox="0 0 535 106" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: sketchtool 55.2 (78181) - https://sketchapp.com -->
<title>promise-reject-1.svg</title>
<desc>Created with sketchtool.</desc>
@ -10,18 +10,18 @@
<tspan x="10" y="22">new Promise(executor)</tspan>
</text>
<text id="state:-&quot;pending&quot;-res" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" line-spacing="20" fill="#8A704D">
<tspan x="21" y="54.4316406">state: "pending"</tspan>
<tspan x="21" y="74.4316406">result: undefined</tspan>
<tspan x="21" y="54">state: "pending"</tspan>
<tspan x="21" y="74">result: undefined</tspan>
</text>
<path id="Line-Copy" d="M297.500387,61.4861111 L198.490385,61.4861111 L198.490385,59.4861111 L297.500387,59.4861111 L297.500387,53.4861111 L311.500387,60.4861111 L297.500387,67.4861111 L297.500387,61.4861111 Z" fill="#EE6B47" fill-rule="nonzero"></path>
<text id="reject(error)" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" fill="#EE6B47">
<tspan x="201.4" y="49">reject(error)</tspan>
<tspan x="212.4" y="49">reject(error)</tspan>
</text>
<rect id="Rectangle-1-Copy-3" stroke="#C74A6C" stroke-width="2" fill="#FCDFE1" x="322" y="34" width="175" height="58"></rect>
<rect id="Rectangle-1-Copy-3" stroke="#C74A6C" stroke-width="2" fill="#FCDFE1" x="353" y="34" width="175" height="58"></rect>
<text id="state:-&quot;rejected&quot;-re" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" line-spacing="20" fill="#727155">
<tspan x="337" y="55.4316406">state: "rejected"</tspan>
<tspan x="337" y="75.4316406">result: error</tspan>
<tspan x="368" y="55">state: "rejected"</tspan>
<tspan x="368" y="75">result: error</tspan>
</text>
<path id="Line-Copy" d="M329,61 L196,61 L196,59 L329,59 L329,53 L343,60 L329,67 L329,61 Z" fill="#EE6B47" fill-rule="nonzero"></path>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

View file

@ -1,26 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="512px" height="106px" viewBox="0 0 512 106" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="538px" height="106px" viewBox="0 0 538 106" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: sketchtool 55.2 (78181) - https://sketchapp.com -->
<title>promise-resolve-1.svg</title>
<desc>Created with sketchtool.</desc>
<g id="promise" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="promise-resolve-1.svg">
<path d="M9,34 L9,92 L184,92 L184,34 L9,34 Z" id="Rectangle-1" stroke="#E8C48E" stroke-width="2" fill="#FFF9EB"></path>
<path d="M6,34 L6,92 L187,92 L187,34 L6,34 Z" id="Rectangle-1" stroke="#E8C48E" stroke-width="2" fill="#FFF9EB"></path>
<text id="new-Promise(executor" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" fill="#707175">
<tspan x="10" y="22">new Promise(executor)</tspan>
<tspan x="8" y="22">new Promise(executor)</tspan>
</text>
<text id="state:-&quot;pending&quot;-res" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" line-spacing="20" fill="#8A704D">
<tspan x="21" y="54.4316406">state: "pending"</tspan>
<tspan x="21" y="74.4316406">result: undefined</tspan>
<tspan x="19" y="54">state: "pending"</tspan>
<tspan x="19" y="74">result: undefined</tspan>
</text>
<path id="Line-Copy" d="M295.500387,61.4861111 L196.490385,61.4861111 L196.490385,59.4861111 L295.500387,59.4861111 L295.500387,53.4861111 L309.500387,60.4861111 L295.500387,67.4861111 L295.500387,61.4861111 Z" fill="#EE6B47" fill-rule="nonzero"></path>
<path id="Line-Copy" d="M329,61 L196,61 L196,59 L329,59 L329,53 L343,60 L329,67 L329,61 Z" fill="#EE6B47" fill-rule="nonzero"></path>
<text id="resolve(&quot;done&quot;)" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" fill="#EE6B47">
<tspan x="192" y="49">resolve("done")</tspan>
<tspan x="200.5" y="49">resolve("done")</tspan>
</text>
<rect id="Rectangle-1-Copy" stroke="#7ED321" stroke-width="2" fill="#FFF9EB" x="323" y="35" width="181" height="57"></rect>
<rect id="Rectangle-1-Copy" stroke="#7ED321" stroke-width="2" fill="#FFF9EB" x="353" y="35" width="181" height="57"></rect>
<text id="state:-&quot;fulfilled&quot;-r" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" line-spacing="20" fill="#417505">
<tspan x="338" y="55.4316406">state: "fulfilled"</tspan>
<tspan x="338" y="75.4316406">result: "done"</tspan>
<tspan x="368" y="55">state: "fulfilled"</tspan>
<tspan x="368" y="75">result: "done"</tspan>
</text>
</g>
</g>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

View file

@ -5,13 +5,13 @@
<desc>Created with sketchtool.</desc>
<g id="promise" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="promise-resolve-reject.svg">
<rect id="Rectangle-1" stroke="#E8C48E" stroke-width="2" fill="#FFF9EB" x="9" y="91" width="170" height="70"></rect>
<rect id="Rectangle-1" stroke="#E8C48E" stroke-width="2" fill="#FFF9EB" x="1" y="91" width="182" height="70"></rect>
<text id="new-Promise(executor" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" fill="#707175">
<tspan x="10" y="82">new Promise(executor)</tspan>
<tspan x="2" y="82">new Promise(executor)</tspan>
</text>
<text id="state:-&quot;pending&quot;-res" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" line-spacing="20" fill="#8A704D">
<tspan x="21" y="115.431641">state: "pending"</tspan>
<tspan x="21" y="135.431641">result: undefined</tspan>
<tspan x="13" y="115.431641">state: "pending"</tspan>
<tspan x="13" y="135.431641">result: undefined</tspan>
</text>
<path id="Line" d="M299.865748,184.482759 L195.673354,136.489909 L196.510091,134.673354 L300.702485,182.666204 L303.212697,177.21654 L313,189.431641 L297.355537,189.932423 L299.865748,184.482759 Z" fill="#EE6B47" fill-rule="nonzero"></path>
<path id="Line-Copy" d="M300.492513,63.3688916 L196.467935,107.310308 L195.689692,105.467935 L299.71427,61.5265189 L297.379539,55.9994011 L313,57 L302.827244,68.8960094 L300.492513,63.3688916 Z" fill="#EE6B47" fill-rule="nonzero"></path>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before After
Before After