working
|
@ -9,40 +9,38 @@ The most obvious example is `setTimeout`, but there are others, like making netw
|
|||
|
||||
## Callbacks
|
||||
|
||||
Consider a function `loadScript` that loads a script:
|
||||
Consider this function `loadScript(src)` that loads a script:
|
||||
|
||||
```js
|
||||
function loadScript(src) {
|
||||
let script = document.createElement('script');
|
||||
script.src = src;
|
||||
document.head.append(script);
|
||||
}
|
||||
```
|
||||
|
||||
When the `<script>` tag is created and `src` is assigned, the browser loads the script and executes it. So, the function works. We can use it like this:
|
||||
When the script element is added to the document, the browser loads it and executes. So, the function works.
|
||||
|
||||
We can use it like this:
|
||||
|
||||
```js
|
||||
// loads and executes the script
|
||||
loadScript('/my/script.js');
|
||||
```
|
||||
|
||||
The function is asynchronous: the script starts loading now, but finishes later, maybe after a few seconds. So, the question is: how can we track the load end? As of now, the function provides no such way.
|
||||
The function is asynchronous: the script starts loading now, but finishes later.
|
||||
|
||||
We'd like to invoke our code after the script is loaded. One of the easiest ways is to add a second argument to `loadScript`: the function that would run on load end.
|
||||
```smart header="Synchronous vs asynchronous"
|
||||
"Synchonous" and "asynchronous" are general programming terms, not specific to JavaScript.
|
||||
|
||||
A *synchronous* action suspends the execution until it's completed. For instance, `alert` and `prompt` are synchronous: the program may not continue until they are finished.
|
||||
|
||||
How can hook on "load completion"?
|
||||
An *asynchronous* action allows the program to continue while it's in progress. For instance, `loadScript` in the example above initiates the script loading, but does not suspend the execution. Other commands may execute while the script is loading.
|
||||
```
|
||||
|
||||
As of now, `loadScript` provides no way to track the load end. How can we execute our own code after the script is loaded?
|
||||
|
||||
From ancient times, Javascript allowed to use callback functions for asynchronous
|
||||
Most asychronous
|
||||
In this chapter we cover how to write callback-based asynchronous code.
|
||||
Let's see a couple of examples, so that we can discover a problem, and then solve it using "promises".
|
||||
|
||||
|
||||
Remember resource load/error events? They are covered in the chapter <info:onload-onerror>.
|
||||
|
||||
Let's say we want to create a function `loadScript` that loads a script and executes our code afterwards.
|
||||
|
||||
It can look like this:
|
||||
Let's allow that by adding a custom function as a second argument to `loadScript`, that should execute at that moment:
|
||||
|
||||
```js
|
||||
function loadScript(src, callback) {
|
||||
|
@ -63,25 +61,24 @@ loadScript('/my/script.js', function(script) {
|
|||
});
|
||||
```
|
||||
|
||||
...And it works, shows `alert` after the script is loaded.
|
||||
|
||||
That is called "a callback API". Our function `loadScript` performs an asynchronous task and we can hook on its completion using the callback function.
|
||||
...And it works, shows the `alert` after the script is loaded.
|
||||
|
||||
## Callback in callback
|
||||
|
||||
What if we need to load two scripts: one more after the first one?
|
||||
What if we need to load two scripts sequentially: the first one, and then the second one after it?
|
||||
|
||||
We can put another `loadScript` inside the callback, like this:
|
||||
We can put the second `loadScript` inside the callback, like this:
|
||||
|
||||
```js
|
||||
loadScript('/my/script.js', function(script) {
|
||||
|
||||
alert(`Cool, the ${script.src} is loaded, let's load one more`);
|
||||
|
||||
*!*
|
||||
loadScript('/my/script2.js', function(script) {
|
||||
|
||||
alert(`Cool, the second script is loaded`);
|
||||
|
||||
});
|
||||
*/!*
|
||||
|
||||
});
|
||||
```
|
||||
|
@ -96,11 +93,11 @@ loadScript('/my/script.js', function(script) {
|
|||
loadScript('/my/script2.js', function(script) {
|
||||
|
||||
if (something) {
|
||||
loadScript('/my/script3.js', function(script) {
|
||||
*!*
|
||||
loadScript('/my/script3.js', function(script) {
|
||||
// ...continue after all scripts are loaded
|
||||
*/!*
|
||||
});
|
||||
*/!*
|
||||
}
|
||||
|
||||
})
|
||||
|
@ -114,7 +111,7 @@ As you can see, a new asynchronous action means one more nesting level.
|
|||
|
||||
In this example we didn't consider errors. What if a script loading failed with an error? Our callback should be able to react on that.
|
||||
|
||||
Here's an improved version of `loadScript` that can handle errors:
|
||||
Here's an improved version of `loadScript` that tracks loading errors:
|
||||
|
||||
```js run
|
||||
function loadScript(src, callback) {
|
||||
|
@ -138,32 +135,34 @@ loadScript('/my/script.js', function(error, script) {
|
|||
if (error) {
|
||||
// handle error
|
||||
} else {
|
||||
// script loaded, go on
|
||||
// script loaded successfully
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The first argument of `callback` is reserved for errors and the second argument for the successful result. That allows to use a single function to pass both success and failure.
|
||||
The first argument of `callback` is reserved for errors, and the second argument is for the successful result.
|
||||
|
||||
## Pyramid of doom
|
||||
|
||||
From the first look it's a viable code. And indeed it is. For one or maybe two nested calls it looks fine.
|
||||
What we've just seen is called a "callback-based" approach to asynchronous programming. We pass a function, and it should run after the process is complete: with an error or a successful result.
|
||||
|
||||
But for multiple asynchronous actions that follow one after another...
|
||||
From the first look it's a viable way of asynchronous coding. And indeed it is. For one or maybe two nested calls it looks fine.
|
||||
|
||||
But for multiple asynchronous actions that follow one after another we'll have a code like this:
|
||||
|
||||
```js
|
||||
loadScript('/my/script1.js', function(error, script) {
|
||||
loadScript('1.js', function(error, script) {
|
||||
|
||||
if (error) {
|
||||
handleError(error);
|
||||
} else {
|
||||
// ...
|
||||
loadScript('/my/script2.js', function(error, script) {
|
||||
loadScript('2.js', function(error, script) {
|
||||
if (error) {
|
||||
handleError(error);
|
||||
} else {
|
||||
// ...
|
||||
loadScript('/my/script3.js', function(error, script) {
|
||||
loadScript('3.js', function(error, script) {
|
||||
if (error) {
|
||||
handleError(error);
|
||||
} else {
|
||||
|
@ -180,35 +179,16 @@ loadScript('/my/script1.js', function(error, script) {
|
|||
```
|
||||
|
||||
In the code above:
|
||||
1. we load `/my/script1.js`, then if there's no error
|
||||
2. we load `/my/script2.js`, then if there's no error
|
||||
3. we load `/my/script3.js`, then if there's no error -- do something else `(*)`.
|
||||
1. We load `1.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 `(*)`.
|
||||
|
||||
The nested calls become 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 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.
|
||||
|
||||
That's sometimes called "callback hell" or "pyramid of doom".
|
||||
|
||||

|
||||
|
||||
See? It grows right with every asynchronous action.
|
||||
The pyramid grows to the right with every asynchronous action. Soon it spirales out of control.
|
||||
|
||||
Compare that with a "regular" synchronous code.
|
||||
|
||||
Just *if* `loadScript` were a regular synchronous function:
|
||||
|
||||
```js
|
||||
try {
|
||||
// assume we get the result in a synchronous manner, without callbacks
|
||||
let script = loadScript('/my/script.js');
|
||||
// ...
|
||||
let script2 = loadScript('/my/script2.js');
|
||||
// ...
|
||||
let script3 = loadScript('/my/script3.js');
|
||||
} catch(err) {
|
||||
handleError(err);
|
||||
}
|
||||
```
|
||||
|
||||
How much cleaner and simpler it is!
|
||||
|
||||
Promises allow to write asynchronous code in a similar way. They are really great at that. Let's study them in the next chapter.
|
||||
Fortunately, there are ways to evade such pyramids. One of them is using "promises", we'll study them in the next chapters.
|
||||
|
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 82 KiB |
|
@ -1,36 +1,41 @@
|
|||
# Promise
|
||||
|
||||
There are many ways to explain promises. Here we'll follow the [specification](https://tc39.github.io/ecma262/#sec-promise-objects), because gives real understanding how things work. Besides, you'll become familiar with the terms.
|
||||
|
||||
First, what a promise is, and then usage patterns.
|
||||
|
||||
[cut]
|
||||
|
||||
## Promise objects
|
||||
|
||||
A promise is an object of the built-in `Promise` class. It has the meaning of the "delayed result".
|
||||
|
||||
The constructor syntax is:
|
||||
|
||||
```js
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
// ...
|
||||
// executor
|
||||
});
|
||||
```
|
||||
|
||||
The function is called *executor*. It is called automatically when the promise is created and can do an asynchronous job, like loading a script, but can be something else.
|
||||
The function passed to `new Promise` is called *executor*. It is called automatically and immediately when the promise is created.
|
||||
|
||||
At the end, it should call one of:
|
||||
So, if you run this, the output is shown immediately:
|
||||
|
||||
```js run
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
alert(resolve); // function () { [native code] }
|
||||
alert(reject); // function () { [native code] }
|
||||
});
|
||||
```
|
||||
|
||||
The string description of `resolve` and `reject` may differ between engines, but they both are functions provided by JavaScript itself. We don't need to create them.
|
||||
|
||||
The executor should do a job, like loading a script, and at the end, it should call one of:
|
||||
|
||||
- `resolve(result)` -- to indicate that the job finished successfully with the `result`.
|
||||
- `reject(error)` -- to indicate that an error occured, and `error` should be the `Error` object (technically can be any value).
|
||||
|
||||
For instance:
|
||||
Like this:
|
||||
|
||||
```js
|
||||
let promise = new Promise(function(*!*resolve*/!*, reject) {
|
||||
// this function is executed automatically when the promise is constructed
|
||||
setTimeout(() => *!*resolve("done!")*/!*, 1000);
|
||||
|
||||
// after 1 second signal that the job is done with the result "done!"
|
||||
setTimeout(() => resolve("done!"), 1000);
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -38,24 +43,31 @@ Or, in case of an error:
|
|||
|
||||
```js
|
||||
let promise = new Promise(function(resolve, *!*reject*/!*) {
|
||||
setTimeout(() => *!*reject(new Error("Woops!"))*/!*, 1000);
|
||||
// after 1 second signal that the job is finished with an error
|
||||
setTimeout(() => reject(new Error("Woops!")), 1000);
|
||||
});
|
||||
```
|
||||
|
||||
Initially, the promise is said to have a "pending" state. Then it has two ways. When `resolve` is called, the state becomes "fulfilled". When `reject` is called, the state becomes "rejected":
|
||||
## Promise state
|
||||
|
||||
The promise object has an internal state. The `Promise` constructor sets it to "pending". Then it may change in two ways.
|
||||
|
||||
When `resolve` is called, the state becomes "fulfilled". When `reject` is called, the state becomes "rejected":
|
||||
|
||||

|
||||
|
||||
The idea of promises is that an external code may react on the state change.
|
||||
```smart
|
||||
The promise that is either resolved or rejected is also called "settled" (as opposed to "pending").
|
||||
```
|
||||
|
||||
The `promise` object provides the method `promise.then(onResolve, onReject)`.
|
||||
The external code may add "handlers" to run when the promise becomes settled.
|
||||
|
||||
Both its arguments are functions:
|
||||
There are two methods for that:
|
||||
|
||||
- `onResolve` is called when the state becomes "fulfilled" and gets the result.
|
||||
- `onReject` is called when the state becomes "rejected" and gets the error.
|
||||
- `promise.then(onResolve)` schedules the function `onResolve` to call when the promise is fulfilled, and it gets the result.
|
||||
- `promise.catch(onReject)` schedules the function `onReject` to call when the promise is rejected, and it gets the error.
|
||||
|
||||
For instance, here `promise.then` outputs the result when it comes:
|
||||
For instance, here's how to react on the successful job completion:
|
||||
|
||||
```js run
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
|
@ -66,8 +78,7 @@ let promise = new Promise(function(resolve, reject) {
|
|||
promise.then(result => alert(result));
|
||||
```
|
||||
|
||||
...And here it shows the error message:
|
||||
|
||||
...And here's the reacton on an error:
|
||||
|
||||
```js run
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
|
@ -75,28 +86,29 @@ let promise = new Promise(function(resolve, reject) {
|
|||
});
|
||||
|
||||
// shows "Woops!" after 1 second
|
||||
promise.then(null, error => alert(error.message));
|
||||
promise.catch(error => alert(error.message));
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
- To handle only a result, we can use single argument: `promise.then(onResolve)`
|
||||
- To handle only an error, we can use a shorthand method `promise.catch(onReject)` -- it's the same as `promise.then(null, onReject)`.
|
||||
|
||||
Here's the example rewritten with `promise.catch`:
|
||||
Also we can call `promise.then(onResolve, onReject)` to set both handlers at once. Technically, `promise.catch(func)` works the same way as `promise.then(null, func)`.
|
||||
|
||||
```js run
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
setTimeout(() => reject(new Error("Woops!")), 1000);
|
||||
setTimeout(() => resolve("done!"), 1000);
|
||||
});
|
||||
|
||||
*!*
|
||||
promise.catch(error => alert(error.message));
|
||||
// set both success and error handlers
|
||||
promise.then(
|
||||
result => alert(result),
|
||||
error => alert(error)
|
||||
);
|
||||
*/!*
|
||||
```
|
||||
|
||||
"What's the benefit?" -- one might ask, "How do we use it?" Let's see an example.
|
||||
So, when we want to do something asynchronously, we can create a promise with the proper executor to do a job, and then add handlers to it. They run when it finishes.
|
||||
|
||||
Let's see more examples to explore the benefits over the callback-based approach.
|
||||
|
||||
## Example: loadScript
|
||||
|
||||
|
@ -116,7 +128,7 @@ function loadScript(src, callback) {
|
|||
|
||||
Let's rewrite it using promises.
|
||||
|
||||
The call to `loadScript(src)` below returns a promise that settles when the loading is complete:
|
||||
The call to `loadScript(src)` below returns a promise that resolves/rejects when the loading is complete:
|
||||
|
||||
```js run
|
||||
function loadScript(src) {
|
||||
|
@ -139,20 +151,24 @@ promise.then(
|
|||
script => alert(`${script.src} is loaded!`),
|
||||
error => alert(`Error: ${error.message}`);
|
||||
);
|
||||
|
||||
promise.then(script => alert('One more handler to do something else with the script'));
|
||||
```
|
||||
|
||||
The benefits compared to the callback syntax:
|
||||
We can immediately see some benefits over the callback-based syntax in the example above:
|
||||
|
||||
- We can add as many `.then` as we want and when we want. Maybe later.
|
||||
- Also we can pass a promise object somewhere else, and new handlers can be added there, so that's extensible.
|
||||
1. We can call `promise.then` as many times as want, so we can add any number of handlers.
|
||||
2. We can call `promise.then` at any time. Maybe much later, when we really need that script.
|
||||
3. The `promise` object can be passed around, new handlers can be added where needed in other parts of the code.
|
||||
|
||||
So promises already give us flexibility. But there's more. We can chain promises, see the next chapter.
|
||||
So promises give us flexibility. But there's more. We can chain promises and use `async` functions and so on. We'll see that in the next chapters.
|
||||
|
||||
Read the notes below for better understanding of promise objects.
|
||||
|
||||
````warn header="Once a promise settles, it can't be changed"
|
||||
When either `resolve` or `reject` is called -- the promise becomes *settled* (fulfilled or rejected), and the state is final. The result is saved in the promise object.
|
||||
When either `resolve` or `reject` is called -- the state change is final. The argument of `resolve/reject` is saved in the promise object.
|
||||
|
||||
Future calls of `resolve/reject` are ignored, there's no way to "re-resolve" or "re-reject" a promise.
|
||||
Future calls of `resolve/reject` are ignored, so there's no way to "re-resolve" or "re-reject" a promise.
|
||||
|
||||
For instance, here only the first `resolve` works:
|
||||
|
||||
|
@ -160,25 +176,58 @@ For instance, here only the first `resolve` works:
|
|||
let promise = new Promise(function(resolve, reject) {
|
||||
resolve("done!"); // immediately fulfill with the result: "done"
|
||||
|
||||
setTimeout(() => resolve("..."), 1000); // ignored
|
||||
setTimeout(() => reject(new Error("..."), 2000)); // ignored
|
||||
// a subsequent resolve is ignored
|
||||
setTimeout(() => resolve("..."), 1000);
|
||||
// a subsequent reject is ignored
|
||||
setTimeout(() => reject(new Error("..."), 2000));
|
||||
});
|
||||
|
||||
// the promise is already fulfilled, so the alert shows up right now
|
||||
promise.then(result => alert(result), () => alert("Never runs"));
|
||||
```
|
||||
````
|
||||
|
||||
|
||||
```smart header="On settled promises `then` runs immediately"
|
||||
As we've seen from the example above, a promise may call resolve/reject without delay. That happens sometimes, if it turns out that there's no asynchronous things to be done.
|
||||
````smart header="A promise may resolve immediately, doesn't have to be asynchronous"
|
||||
As we've seen from the example above, a promise may call resolve/reject without delay:
|
||||
|
||||
When the promise is settled (resolved or rejected), subsequent `promise.then` callbacks are executed immediately.
|
||||
```js run
|
||||
new Promise(function(resolve, reject) {
|
||||
resolve("done!"); // immediately fulfill with the result: "done"
|
||||
}).then(alert);
|
||||
```
|
||||
|
||||
That's normal, sometimes it turns out that there's no asynchronous job to be done. For instance, if we have a cached result.
|
||||
````
|
||||
|
||||
````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 resolved/rejected, then subsequent `promise.then/catch` handlers are executed immediately.
|
||||
|
||||
To be precise -- they are still asynchronous, but run as soon as possible, similar to `setTimeout(...,0)`.
|
||||
|
||||
For instance, here we'll first see "code end", and then "done!":
|
||||
|
||||
```js run
|
||||
// the promise is in the "resolved" state immediately
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
resolve("done!");
|
||||
});
|
||||
|
||||
// the handler runs as soon as possible, but asynchronously, like setTimeout(...,0)
|
||||
promise.then(alert);
|
||||
|
||||
alert('code end');
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
````smart header="Functions resolve/reject have only one argument"
|
||||
Functions `resolve/reject` accept only one argument.
|
||||
|
||||
We can call them without any arguments: `resolve()`, that makes the result `undefined`:
|
||||
We can call them without any arguments too. The call `resolve()` makes the result `undefined`:
|
||||
|
||||
```js run
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
|
@ -188,17 +237,21 @@ let promise = new Promise(function(resolve, reject) {
|
|||
promise.then(result => alert(result)); // undefined
|
||||
```
|
||||
|
||||
...But if we pass more arguments: `resolve(1, 2, 3)`, then all arguments after the first one are ignored. A promise may have only one value as a result/error.
|
||||
...But if we pass many arguments: `resolve(1, 2, 3)`, then all arguments after the first one are ignored. A promise may have only one value as a result/error.
|
||||
|
||||
Use objects and destructuring if you need to pass many values, like this:
|
||||
|
||||
```js run
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
resolve({name: "John", age: 25}); // two values packed in an object
|
||||
let name = "John";
|
||||
let age = 25;
|
||||
|
||||
resolve({name, age}); // "pack" the values in an object
|
||||
});
|
||||
|
||||
// destructuring the object into name and age variables
|
||||
// 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
|
||||
})
|
||||
```
|
||||
|
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
@ -1,18 +1,25 @@
|
|||
|
||||
# Promises chaining
|
||||
|
||||
There are many great things about promises. We're only starting.
|
||||
Let's formulate the problem mentioned in the chapter <info:callback-hell>:
|
||||
|
||||
Now we'll cover promises chaining. They allow to build sequences of asynchronous actions.
|
||||
- We have a sequence of tasks to be done one after another. For instance, loading scripts. The next task may need the result of the previous one.
|
||||
- How to code it well?
|
||||
|
||||
[cut]
|
||||
Promises can cover that need in two ways:
|
||||
|
||||
Here's the idea:
|
||||
1. Promises chaining.
|
||||
2. Async functions.
|
||||
|
||||
Let's see the first way in this chapter and the second one in the next.
|
||||
|
||||
Promises chaining looks like this:
|
||||
|
||||
```js run
|
||||
new Promise(function(resolve, reject) {
|
||||
// do a job...
|
||||
|
||||
setTimeout(() => resolve(1), 1000);
|
||||
|
||||
}).then(function(result) {
|
||||
|
||||
alert(result); // 1
|
||||
|
@ -32,12 +39,132 @@ new Promise(function(resolve, reject) {
|
|||
// ...
|
||||
```
|
||||
|
||||
As you can see:
|
||||
As we can see, a call to `promise.then` returns a promise, that we can use again for `.then`. A value returned by `.then` becomes a result in the next `.then`. So in the example above we have a sequence of results: `1` -> `2` -> `4`.
|
||||
|
||||
Please note that chaining `.then` is not the same as many `.then` on a single promise, like below:
|
||||
|
||||
```js run
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
setTimeout(() => resolve(1), 1000);
|
||||
});
|
||||
|
||||
promise.then(function(result) {
|
||||
alert(result); // 1
|
||||
return result * 2;
|
||||
});
|
||||
|
||||
promise.then(function(result) {
|
||||
alert(result); // 1
|
||||
return result * 2;
|
||||
});
|
||||
|
||||
promise.then(function(result) {
|
||||
alert(result); // 1
|
||||
return result * 2;
|
||||
});
|
||||
```
|
||||
|
||||
In the code above, all `.then` are on the same promise, so all of them get the same result -- the result of that promise. And all `alert` show the same: 1.
|
||||
|
||||
If we want to use the value returned by a handler of `.then`, then we should add a new `.then` after it (to chain).
|
||||
|
||||
## Returning promises
|
||||
|
||||
Normally, the value returned by a handler is passed to the next `.then`. But there's an exception. If the returned value is a promise, then further execution is suspended till it settles. And then the result of that promise is used.
|
||||
|
||||
Let's see it in action here:
|
||||
|
||||
```js run
|
||||
new Promise(function(resolve, reject) {
|
||||
|
||||
setTimeout(() => resolve(1), 1000);
|
||||
|
||||
}).then(function(result) {
|
||||
|
||||
alert(result); // 1
|
||||
|
||||
*!*
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve(result * 2), 1000);
|
||||
});
|
||||
*/!*
|
||||
|
||||
}).then(function(result) {
|
||||
|
||||
alert(result); // 2
|
||||
|
||||
*!*
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve(result * 2), 1000);
|
||||
});
|
||||
*/!*
|
||||
|
||||
}).then(function(result) {
|
||||
|
||||
alert(result); // 4
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
Now we have the same 1 -> 2 > 4 output, but with 1 second delay between each.
|
||||
|
||||
When we return `new Promise(…)`, the next `.then` in the chain is executed when it settles and gets its result.
|
||||
|
||||
Let's use it to `loadScript` multiple scripts one by one:
|
||||
|
||||
```js run
|
||||
function loadScript(src) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let script = document.createElement('script');
|
||||
script.src = src;
|
||||
|
||||
script.onload = () => resolve(script);
|
||||
script.onerror = () => reject(new Error("Script load error: " + src));
|
||||
|
||||
document.head.append(script);
|
||||
});
|
||||
}
|
||||
|
||||
loadScript("/article/promise-chaining/one.js")
|
||||
.then(function(script) {
|
||||
return loadScript("/article/promise-chaining/two.js");
|
||||
})
|
||||
.then(function(script) {
|
||||
return loadScript("/article/promise-chaining/three.js");
|
||||
})
|
||||
.then(function(script) {
|
||||
// use variables declared in scripts
|
||||
// to show that they indeed loaded
|
||||
alert("Done: " + (one + two + three));
|
||||
});
|
||||
```
|
||||
|
||||
The code totally evades the pyramid of doom. We can add more asynchronous actions to the chain, and the code is still "flat".
|
||||
|
||||
## Inheriting from promise, thenables, error handling?
|
||||
|
||||
An object that has a method called `.then` is called a "thenable".
|
||||
|
||||
Instead of checking if something is `instanceof Promise`, we should usually check it for being thenable, and if it is, then treat it as a promise ("duck typing").
|
||||
|
||||
JavaScript specification also checks the value returned by a handler for being a thenable, not exactly a promise, when it decides whether to pass it along the chain or wait for the result. So in the examples above we could use custom thenables instead of `Promise` instances.
|
||||
|
||||
For instance, native promises give no way to "abort" the execution. The `loadScript` above cannot "cancel" script loading, just because there's no `.abort` method on promises, we can only listen for the state change using `.then/catch`.
|
||||
|
||||
Let's
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Error handling
|
||||
|
||||
|
||||
|
||||
- Calls to `.then` can be chained -- that's because `promise.then` returns a promise.
|
||||
- A value returned by `.then` becomes a result in the next `.then`.
|
||||
|
||||
If there's an error, it is also passed down the chain:
|
||||
|
||||
|
||||
```js run
|
||||
|
|
1
8-async/03-promise-chaining/one.js
Normal file
|
@ -0,0 +1 @@
|
|||
let one = 1;
|
1
8-async/03-promise-chaining/three.js
Normal file
|
@ -0,0 +1 @@
|
|||
let three = 3;
|
1
8-async/03-promise-chaining/two.js
Normal file
|
@ -0,0 +1 @@
|
|||
let two = 2;
|
23
archive/thenable.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
function loadScript(src) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let script = document.createElement('script');
|
||||
script.src = src;
|
||||
|
||||
script.onload = () => resolve(script);
|
||||
script.onerror = () => reject(new Error("Script load error: " + src));
|
||||
|
||||
document.head.append(script);
|
||||
});
|
||||
}
|
||||
loadScript("/article/promise-chaining/one.js")
|
||||
.then(script => {
|
||||
return {
|
||||
then(resolve, reject) {
|
||||
setTimeout(() => resolve(script), 1000);
|
||||
}
|
||||
};
|
||||
})
|
||||
.then(function(script) {
|
||||
alert(one);
|
||||
});
|
||||
|