You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 6-async/02-promise-basics/article.md
+69-55Lines changed: 69 additions & 55 deletions
Original file line number
Diff line number
Diff line change
@@ -1,18 +1,18 @@
1
1
# Promise
2
2
3
-
Imagine that you're a top singer, and fans ask for your next upcoming single day and night.
3
+
Imagine that you're a top singer, and fans ask day and night for your upcoming single.
4
4
5
-
To get a relief, you 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 if something goes very wrong, so that the song won't be published ever, then they are also to be notified.
5
+
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.
6
6
7
7
Everyone is happy: you, because the people don't crowd you any more, and fans, because they won't miss the single.
8
8
9
-
That was a real-life analogy for things we often have in programming:
9
+
This is a real-life analogy for things we often have in programming:
10
10
11
-
1. A "producing code" that does something and needs time. For instance, it loads a remote script. That's a "singer".
12
-
2. A "consuming code" wants the result when it's ready. Many functions may need that result. These are "fans".
13
-
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.
11
+
1. A "producing code" that does something and takes time. For instance, the code loads a remote script. That's a "singer".
12
+
2. A "consuming code" that wants the result of the "producing code" once it's ready. Many functions (in your "consuming code") may need that result. These are the "fans".
13
+
3. A *promise* is a special JavaScript object that links the "producing code" and the "consuming code" together. In terms of our analogy: this is the "subscription list". The "producing code" takes whatever time it needs to produce the promised result, and the "promise" makes that result available to all of the subscribed code when it's ready.
14
14
15
-
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.
15
+
The analogy isn't terribly accurate, because JavaScript promises are more complex than a simple subscription list: they have additional features and limitations. But this will serve for an introduction.
16
16
17
17
The constructor syntax for a promise object is:
18
18
@@ -22,25 +22,25 @@ let promise = new Promise(function(resolve, reject) {
22
22
});
23
23
```
24
24
25
-
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".
25
+
The function passed to `new Promise` is called the *executor*. When the promise is created, this executor function is called (or, run) automatically. It contains the producing code, that should eventually produce a result. In terms of the analogy above: the executor is the "singer".
26
26
27
27
The resulting `promise` object has internal properties:
28
28
29
-
-`state`-- initially is "pending", then changes to "fulfilled" or "rejected",
30
-
-`result`-- an arbitrary value, initially `undefined`.
29
+
-`state`— initially "pending", then changes to either "fulfilled" or "rejected",
30
+
-`result`— an arbitrary value of your choosing, initially `undefined`.
31
31
32
-
When the executor finishes the job, it should call one of:
32
+
When the executor finishes the job, it should call one of the following functions:
33
33
34
-
-`resolve(value)`-- to indicate that the job finished successfully:
34
+
-`resolve(value)`— to indicate that the job finished successfully:
35
35
- sets `state` to `"fulfilled"`,
36
36
- sets `result` to `value`.
37
-
-`reject(error)`-- to indicate that an error occurred:
37
+
-`reject(error)`— to indicate that an error occurred:
38
38
- sets `state` to `"rejected"`,
39
39
- sets `result` to `error`.
40
40
41
41

42
42
43
-
Here's a simple executor, to gather that all together:
43
+
To gather all of that together: here's an example of a Promise constructor and a simple executor function with its "producing code" (the `setTimeout`):
44
44
45
45
```js run
46
46
let promise =newPromise(function(resolve, reject) {
@@ -56,16 +56,16 @@ let promise = new Promise(function(resolve, reject) {
56
56
57
57
We can see two things by running the code above:
58
58
59
-
1. The executor is called automatically and immediately (by `new Promise`).
60
-
2. The executor receives two arguments: `resolve` and `reject`-- these functions come from JavaScript engine. We don't need to create them. Insteadthe executor should call them when ready.
59
+
1. The executor is called automatically and immediately (by the `new Promise`).
60
+
2. The executor receives two arguments: `resolve` and `reject`— these functions are pre-defined by the JavaScript engine. So we don't need to create them. Instead, we should write the executor to call them when ready.
61
61
62
-
After one second of thinking the executor calls `resolve("done")` to produce the result:
62
+
After one second of "processing" the executor calls `resolve("done")` to produce the result:
63
63
64
64

65
65
66
-
That was an example of the "successful job completion".
66
+
That was an example of a successful job completion, a "fulfilled promise".
67
67
68
-
And now an example where the executor rejects promise with an error:
68
+
And now an example of the executor rejecting the promise with an error:
69
69
70
70
```js
71
71
let promise =newPromise(function(resolve, reject) {
@@ -76,12 +76,12 @@ let promise = new Promise(function(resolve, reject) {
76
76
77
77

78
78
79
-
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.
79
+
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.
80
80
81
-
The promise that is either resolved or rejected is called "settled", as opposed to a "pending" promise.
81
+
The Promise that is either resolved or rejected is called "settled", as opposed to a "pending" Promise.
82
82
83
-
````smart header="There can be only one result or an error"
84
-
The executor should call only one `resolve` or `reject`. The promise state change is final.
83
+
````smart header="There can be only a single result or an error"
84
+
The executor should call only one `resolve` or `reject`. The promise's state change is final.
85
85
86
86
All further calls of `resolve` and `reject` are ignored:
87
87
@@ -94,46 +94,53 @@ let promise = new Promise(function(resolve, reject) {
94
94
});
95
95
```
96
96
97
-
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.
97
+
The idea is that a job done by the executor may have only one result or an error.
98
98
99
-
Also if we call `resolve/reject` with more then one argument -- only the first argument is used, the next ones are ignored.
99
+
Further, `resolve`/`reject` expect only one argument and will ignore additional arguments.
100
100
````
101
101
102
102
```smart header="Reject with `Error` objects"
103
-
Technically we can call `reject` (just like `resolve`) with any type of argument. But it's recommended to use `Error` objects in `reject`(or inherit from them). The reasoning for that will become obvious soon.
103
+
Technically, we can pass any type of argument to `reject` (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.
104
104
```
105
105
106
-
````smart header="Resolve/reject can be immediate"
107
-
In practice an executor usually does something asynchronously and calls `resolve/reject` after some time, but it doesn't have to. We can call `resolve` or `reject` immediately, like this:
In practice, an executor usually does something asynchronously and calls `resolve`/`reject` after some time, but it doesn't have to. We can call `resolve` or `reject` immediately, like this:
108
108
109
109
```js
110
110
let promise = new Promise(function(resolve, reject) {
111
111
resolve(123); // immediately give the result: 123
112
112
});
113
113
```
114
114
115
-
For instance, it happens when we start to do a job and then see that everything has already been done. Technically that's fine: we have a resolved promise right now.
115
+
For instance, this might happen when we start to do a job but then see that everything has already been completed. That is fine: we immediately have a resolved Promise.
116
116
````
117
117
118
118
```smart header="The `state` and `result` are internal"
119
-
Properties `state` and `result` of a promise object are internal. We can't directly access them from our code, but we can use methods `.then/catch` for that, they are described below.
119
+
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` for that. They are described below.
120
120
```
121
121
122
-
## Consumers: ".then" and ".catch"
123
-
124
-
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`.
122
+
## Consumers: `.then and `.catch`
125
123
124
+
A Promise object serves as a link between the executor (the "producing code" or "singer) and the consuming functions (the "fans"), which will receive the result or error. Consuming functions can be registered (subscribed) using the methods `.then` and `.catch`.
126
125
127
126
The syntax of `.then` is:
128
127
129
128
```js
130
129
promise.then(
131
-
function(result) { /* handle a successful result */ },
132
-
function(error) { /* handle an error */ }
130
+
function(result) { *!*/* handle a successful result */*/!* },
131
+
function(error) { *!*/* handle an error */*/!* }
133
132
);
134
133
```
135
134
136
-
The first function argument runs when the promise is resolved and gets the result, and the second one -- when it's rejected and gets the error.
135
+
The first function argument:
136
+
137
+
1. runs when the Promise is resolved, and
138
+
2. receives the result.
139
+
140
+
The second function argument:
141
+
142
+
1. runs when the Promise is rejected, and
143
+
2. receives the error.
137
144
138
145
For instance:
139
146
@@ -151,7 +158,7 @@ promise.then(
151
158
);
152
159
```
153
160
154
-
In case of a rejection:
161
+
In the case of a rejection:
155
162
156
163
```js run
157
164
let promise = new Promise(function(resolve, reject) {
@@ -167,7 +174,7 @@ promise.then(
167
174
);
168
175
```
169
176
170
-
If we're interested only in successful completions, then we can provide only one argument to `.then`:
177
+
If we're interested only in successful completions, then we can provide only one function argument to `.then`:
171
178
172
179
```js run
173
180
let promise = new Promise(resolve => {
@@ -179,7 +186,7 @@ promise.then(alert); // shows "done!" after 1 second
179
186
*/!*
180
187
```
181
188
182
-
If we're interested only in errors, then we can use `.then(null, function)` or an "alias" to it: `.catch(function)`
189
+
If we're interested only in errors, then we can use `.catch(errorHandlingFunction)`, which is exactly the same as `.then(null, errorHandlingFunction)`.
183
190
184
191
185
192
```js run
@@ -208,32 +215,39 @@ promise.then(alert); // done! (shows up right now)
208
215
That's handy for jobs that may sometimes require time and sometimes finish immediately. The handler is guaranteed to run in both cases.
209
216
````
210
217
211
-
````smart header="Handlers of `.then/catch` are always asynchronous"
212
-
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)`.
218
+
````smart header="Handlers of `.then`/`.catch` are always asynchronous"
219
+
Even when the Promise is immediately resolved, code which occurs on lines *below* your `.then`/`.catch` may still execute first.
213
220
214
-
In other words, when `.then(handler)` is going to trigger, it does something like `setTimeout(handler, 0)` instead.
221
+
When it is time for the function you've passed to `.then`/`.catch`to execute, the JavaScript engine puts it at the end of an internal execution queue.
215
222
216
-
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.
223
+
The JavaScript engine doesn't wait very long for an operation to finish before moving on to do other things. Everything is racing to get done. So as you gain experience, you will encounter situations where some lines of code are still "in process" while *later* lines have already started. In the example code below, you can imagine that the events might occur in this order, behind the scenes:
224
+
225
+
1. The new Promise object is constructed at Line 3, and the executor is passed on to it.
226
+
2. The subscriber at Line 5 (the call to `alert`) is registered with the Promise.
227
+
3. The executor is finally run by the Promise object and immediately resolves.
228
+
4. The second `alert` call, at Line 7, is executed.
229
+
6. The Promise notices that it has been resolved and therefore executes the registered call to `alert` from Line 5.
217
230
218
231
```js run
219
-
// an immediately resolved promise
220
-
let promise =newPromise(resolve=>resolve("done!"));
232
+
// an "immediately" resolved Promise
233
+
constexecutor=resolve=>resolve("done!");
234
+
constpromise=newPromise(executor);
221
235
222
-
promise.then(alert); //done! (right after the current code finishes)
236
+
promise.then(alert); //this alert shows last
223
237
224
238
alert("code finished"); // this alert shows first
225
239
```
226
240
227
-
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.
241
+
So the code *after*`.then`ends up always running *before* the Promise's subscribers, even in the case of an immediately-resolved Promise. Usually that's unimportant, in some scenarios it may matter a great deal.
228
242
````
229
243
230
-
Now let's see more practical examples how promises can help us in writing asynchronous code.
244
+
Now, let's see more practical examples of how promises can help us to write asynchronous code.
231
245
232
246
## Example: loadScript
233
247
234
248
We've got the `loadScript` function for loading a script from the previous chapter.
235
249
236
-
Here's the callback-based variant, just to remind it:
250
+
Here's the callback-based variant, just to remind us of it:
237
251
238
252
```js
239
253
function loadScript(src, callback) {
@@ -247,9 +261,9 @@ function loadScript(src, callback) {
247
261
}
248
262
```
249
263
250
-
Let's rewrite it using promises.
264
+
Let's rewrite it using Promises.
251
265
252
-
The new function `loadScript` will not require a callback. Instead it will create and return a promise object that settles when the loading is complete. The outer code can add handlers to it using `.then`:
266
+
The new function `loadScript` will not require a callback. Instead, it will create and return a Promise object that resolves when the loading is complete. The outer code can add handlers (subscribing functions) to it using `.then`:
253
267
254
268
```js run
255
269
function loadScript(src) {
@@ -278,13 +292,13 @@ promise.then(
278
292
promise.then(script => alert('One more handler to do something else!'));
279
293
```
280
294
281
-
We can immediately see few benefits over the callback-based syntax:
295
+
We can immediately see a few benefits over the callback-based pattern:
282
296
283
297
```compare minus="Callbacks" plus="Promises"
284
298
- 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.
285
299
- There can be only one callback.
286
-
+ Promises allow us to code things in the natural order. First we run `loadScript`, and `.then` write what to do with the result.
287
-
+ We can call `.then` on a promise as many times as we want, at any time later.
300
+
+ Promises allow us to do things in the natural order. First, we run `loadScript`, and `.then` we write what to do with the result.
301
+
+ We can call `.then` on a Promise as many times as we want. Each time, we're adding a new "fan", a new subscribing function, to the "subscription list". More about this in the next section: [Promise Chaining](/promise-chaining).
288
302
```
289
303
290
-
So promises already give us better code flow and flexibility. But there's more. We'll see that in the next chapters.
304
+
So Promises already give us better code flow and flexibility. But there's more. We'll see that in the next chapters.
0 commit comments