up
This commit is contained in:
parent
455d300d8d
commit
58c04e2d2d
4 changed files with 99 additions and 67 deletions
|
@ -8,9 +8,9 @@ Everyone is happy: you, because the people don't crowd you any more, and fans, b
|
|||
|
||||
That was a real-life analogy for things we often have in programming:
|
||||
|
||||
1. A "producing code" that does something and needs time. For instance, it loads a remote script. That's the "singer".
|
||||
2. A "consuming code" wants the result when it's ready. Many functions may need that result. So that's the "fans".
|
||||
3. A *promise* is a special JavaScript object that links them together. That's the "list". The producing code makes it and gives to everyone, so that they can subscribe for the result.
|
||||
1. A "producing code" that does something and needs time. For instance, it loads a remote script. That's a "singer".
|
||||
2. A "consuming code" wants the result when it's ready. Many functions may need that result. These are "fans".
|
||||
3. A *promise* is a special JavaScript object that links them together. That's a "list". The producing code creates it and gives to everyone, so that they can subscribe for the result.
|
||||
|
||||
The analogy isn't very accurate, because JavaScript promises are more complex than a simple list: they have additional features and limitations. But still they are alike.
|
||||
|
||||
|
@ -18,19 +18,17 @@ The constructor syntax for a promise object is:
|
|||
|
||||
```js
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
// producing code
|
||||
// executor (the producing code, "singer")
|
||||
});
|
||||
```
|
||||
|
||||
The function passed to `new Promise` is called *executor*. When the promise is created, it's called automatically. It contains the producing code, that should eventually finish with a result. In terms of the analogy above, the executor is a "singer".
|
||||
|
||||
The `promise` object has internal properties:
|
||||
The resulting `promise` object has internal properties:
|
||||
|
||||
- `state` -- initially is "pending", then changes to "fulfilled" or "rejected",
|
||||
- `result` -- an arbitrary value, initially `undefined`.
|
||||
|
||||
Both `state` and `result` are managed by the executor.
|
||||
|
||||
When the executor finishes the job, it should call one of:
|
||||
|
||||
- `resolve(value)` -- to indicate that the job finished successfully:
|
||||
|
@ -42,7 +40,7 @@ When the executor finishes the job, it should call one of:
|
|||
|
||||

|
||||
|
||||
Here's a simple executor, just to see it in action:
|
||||
Here's a simple executor, to gather that all together:
|
||||
|
||||
```js run
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
|
@ -59,13 +57,15 @@ let promise = new Promise(function(resolve, reject) {
|
|||
We can see two things by running the code above:
|
||||
|
||||
1. The executor is called automatically and immediately (by `new Promise`).
|
||||
2. Executor arguments `resolve` and `reject` are functions that come from JavaScript engine. We don't need to create them (their string representation may vary between JavaScript engines). Instead the executor should call them when ready.
|
||||
2. The executor receives two arguments: `resolve` and `reject` -- these functions come from JavaScript engine. We don't need to create them. Instead the executor should call them when ready.
|
||||
|
||||
After one second of thinking it calls `resolve("done")` to produce the result:
|
||||
After one second of thinking the executor calls `resolve("done")` to produce the result:
|
||||
|
||||

|
||||
|
||||
The next example rejects the promise with an error:
|
||||
That was an example of the "successful job completion".
|
||||
|
||||
And now an example where the executor rejects promise with an error:
|
||||
|
||||
```js
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
|
@ -76,10 +76,12 @@ let promise = new Promise(function(resolve, reject) {
|
|||
|
||||

|
||||
|
||||
To summarize, the executor should do a job (something that takes time usually) and then call `resolve` or `reject` to change the state of the corresponding promise object.
|
||||
|
||||
The promise that is either resolved or rejected is called "settled", as opposed to a "pending" promise.
|
||||
|
||||
````smart header="There can be only one result or an error"
|
||||
The executor should call only one `resolve` or `reject`. And the promise state change is final.
|
||||
The executor should call only one `resolve` or `reject`. The promise state change is final.
|
||||
|
||||
All further calls of `resolve` and `reject` are ignored:
|
||||
|
||||
|
@ -92,7 +94,7 @@ let promise = new Promise(function(resolve, reject) {
|
|||
});
|
||||
```
|
||||
|
||||
The idea is that a job done by the executor may have only one result or an error. In programming, there exist other data structures that allow many "flowing" results, for instance streams and queues. They have their own advantages and disadvantages versus promises. They are not supported by JavaScript core and lack certain language features that promises provide, so we don't cover them here, to concentrate on promises.
|
||||
The idea is that a job done by the executor may have only one result or an error. In programming, there exist other data structures that allow many "flowing" results, for instance streams and queues. They have their own advantages and disadvantages versus promises. They are not supported by JavaScript core and lack certain language features that promises provide, we don't cover them here to concentrate on promises.
|
||||
|
||||
Also we if we call `resolve/reject` with more then one argument -- only the first argument is used, the next ones are ignored.
|
||||
````
|
||||
|
@ -119,7 +121,7 @@ Properties `state` and `result` of a promise object are internal. We can't direc
|
|||
|
||||
## Consumers: ".then" and ".catch"
|
||||
|
||||
A promise object serves as a link between the producing code (executor) and the consuming code. "Consumers" -- functions that want to receive the result/error can be registered using methods `promise.then` and `promise.catch`.
|
||||
A promise object serves as a link between the producing code (executor) and the consuming functions -- those that want to receive the result/error. Consuming functions can be registered using methods `promise.then` and `promise.catch`.
|
||||
|
||||
|
||||
The syntax of `.then` is:
|
||||
|
@ -191,7 +193,7 @@ promise.catch(alert); // shows "Error: Whoops!" after 1 second
|
|||
*/!*
|
||||
```
|
||||
|
||||
So, `.catch(f)` is a complete analog of `.then(null, f)`, just a shorthand.
|
||||
The call `.catch(f)` is a complete analog of `.then(null, f)`, it's just a shorthand.
|
||||
|
||||
````smart header="On settled promises `then` runs immediately"
|
||||
If a promise is pending, `.then/catch` handlers wait for the result. Otherwise, if a promise has already settled, they execute immediately:
|
||||
|
@ -203,11 +205,15 @@ let promise = new Promise(resolve => resolve("done!"));
|
|||
promise.then(alert); // done! (shows up right now)
|
||||
```
|
||||
|
||||
That's good for jobs that may sometimes require time and sometimes finish immediately. The handler is guaranteed to run in both cases.
|
||||
That's handy for jobs that may sometimes require time and sometimes finish immediately. The handler is guaranteed to run in both cases.
|
||||
````
|
||||
|
||||
To be precise, `.then/catch` queue up and are taken from the queue asynchronously when the current code finishes, like `setTimeout(..., 0)`.
|
||||
````smart header="Handlers of `.then/catch` are always asynchronous"
|
||||
To be even more precise, when `.then/catch` handler should execute, it first gets into an internal queue. The JavaScript engine takes handlers from the queue and executes when the current code finishes, similar to `setTimeout(..., 0)`.
|
||||
|
||||
So here the `alert` call is "queued" and runs immediately after the code finishes:
|
||||
In other words, when `.then(handler)` is going to trigger, it does something like `setTimeout(handler, 0)` instead.
|
||||
|
||||
In the example below the promise is immediately resolved, so `.then(alert)` triggers right now: the `alert` call is queued and runs immediately after the code finishes.
|
||||
|
||||
```js run
|
||||
// an immediately resolved promise
|
||||
|
@ -218,14 +224,14 @@ promise.then(alert); // done! (right after the current code finishes)
|
|||
alert("code finished"); // this alert shows first
|
||||
```
|
||||
|
||||
In practice the time for the code to finish execution is usually negligible. But the code after `.then` always executes before the `.then` handler (even in the case of a pre-resolved promise), that could matter.
|
||||
So the code after `.then` always executes before the handler (even in the case of a pre-resolved promise). Usually that's unimportant, in some scenarios may matter.
|
||||
````
|
||||
|
||||
Now let's see more practical examples to see how promises can help us in writing asynchronous code.
|
||||
Now let's see more practical examples how promises can help us in writing asynchronous code.
|
||||
|
||||
## Example: loadScript
|
||||
|
||||
We have the `loadScript` function for loading a script from the previous chapter.
|
||||
We've got the `loadScript` function for loading a script from the previous chapter.
|
||||
|
||||
Here's the callback-based variant, just to remind it:
|
||||
|
||||
|
@ -263,6 +269,7 @@ 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}`)
|
||||
|
@ -276,8 +283,8 @@ We can immediately see few benefits over the callback-based syntax:
|
|||
```compare minus="Callbacks" plus="Promises"
|
||||
- We must have a ready `callback` function when calling `loadScript`. In other words, we must know what to do with the result *before* `loadScript` is called.
|
||||
- There can be only one callback.
|
||||
+ Promises allow us to code more naturally. First we run `loadScript`, and `.then` code what we do with the result.
|
||||
+ We can call `.then` as many times as we want, at any point of time later.
|
||||
+ Promises allow us to code things in the natural order. First we run `loadScript`, and `.then` write what to do with the result.
|
||||
+ We can call `.then` on a promise as many times as we want, at any time later.
|
||||
```
|
||||
|
||||
So promises already give us better code flow and flexibility. But there's more. We'll see that in the next chapters.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue