promises
This commit is contained in:
parent
a68022a8d6
commit
04b2fcfba2
17 changed files with 304 additions and 137 deletions
|
@ -1,63 +1,77 @@
|
|||
# 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:
|
||||
|
||||
```js
|
||||
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`.
|
||||
- `reject(error)` -- to indicate that an error occured: sets `state` to `"rejected"` and `result` to `error`.
|
||||
|
||||

|
||||
|
||||
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
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
// the function is executed automatically when the promise is constructed
|
||||
|
||||
alert(resolve); // 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`.
|
||||
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.
|
||||
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.
|
||||
|
||||
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
|
||||
let promise = new Promise(function(*!*resolve*/!*, reject) {
|
||||
// this function is executed automatically when the promise is constructed
|
||||
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
// after 1 second signal that the job is done with the result "done!"
|
||||
setTimeout(() => resolve("done!"), 1000);
|
||||
setTimeout(() => *!*resolve("done!")*/!*, 1000);
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
Here we wait 1 second and then reject the promise with an error:
|
||||
The next example rejects the promise with an error:
|
||||
|
||||
```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
|
||||
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".
|
||||
|
||||
````smart header="There can be only one result or an error"
|
||||
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
|
||||
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"
|
||||
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"
|
||||
|
@ -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"
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
promise.then(
|
||||
function(result) { /* process a sucessful result */ },
|
||||
function(error) { /* process an error */ }
|
||||
function(result) { /* handle a sucessful result */ },
|
||||
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
|
||||
|
@ -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
|
||||
|
||||
|
@ -205,14 +238,15 @@ function loadScript(src) {
|
|||
document.head.append(script);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
*!*
|
||||
// Usage:
|
||||
*/!*
|
||||
Usage:
|
||||
|
||||
```js run
|
||||
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js");
|
||||
promise.then(
|
||||
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!'));
|
||||
|
@ -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.
|
||||
```
|
||||
|
||||
|
||||
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
|
||||
})
|
||||
```
|
||||
|
||||
````
|
||||
So promises give us better code flow and flexibility. But there's more. We'll see that in the next chapters.
|
||||
|
|
13
8-async/02-promise-basics/head.html
Normal file
13
8-async/02-promise-basics/head.html
Normal 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>
|
Loading…
Add table
Add a link
Reference in a new issue