add promise queue
This commit is contained in:
parent
1373f6158c
commit
16cfa3037b
27 changed files with 160 additions and 36 deletions
|
@ -214,9 +214,13 @@ The call `.catch(f)` is a complete analog of `.then(null, f)`, it's just a short
|
|||
|
||||
### finally
|
||||
|
||||
The call `.finally(f)` is similar to `.then(f, f)`, it always runs when the promise is settled: be it resolve or reject.
|
||||
Just like there's a finally clause in a regular `try {...} catch {...}`, there's `finally` in promises.
|
||||
|
||||
The idea is that we can perform cleanup in it, e.g. stop our loading indicators in `finally`, as they are not needed any more, like this:
|
||||
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.
|
||||
|
||||
It is a good handler to perform cleanup, e.g. to stop our loading indicators in `finally`, as they are not needed any more, no matter what the outcome is.
|
||||
|
||||
Like this:
|
||||
|
||||
```js
|
||||
new Promise((resolve, reject) => {
|
||||
|
@ -231,7 +235,7 @@ new Promise((resolve, reject) => {
|
|||
|
||||
It's not exactly an alias though. There are several important differences:
|
||||
|
||||
1. A `finally` handler has no arguments. In `finally` we don't know whether the promise is successful or not. We shouldn't need to know it, as our task is usually to perform "general" finalizing procedures.
|
||||
1. A `finally` handler has no arguments. In `finally` we don't know whether the promise is successful or not. That's all right, as our task is usually to perform "general" finalizing procedures.
|
||||
2. Finally passes through results and errors to the next handler.
|
||||
|
||||
For instance, here the result is passed through `finally` to `then`:
|
||||
|
@ -240,7 +244,7 @@ It's not exactly an alias though. There are several important differences:
|
|||
setTimeout(() => resolve("result"), 2000)
|
||||
})
|
||||
.finally(() => alert("Promise ready"))
|
||||
.then(result => alert(result)); // result
|
||||
.then(result => alert(result)); // <-- .then handles the result
|
||||
```
|
||||
|
||||
And here there's an error in the promise, passed through `finally` to `catch`:
|
||||
|
@ -250,14 +254,14 @@ It's not exactly an alias though. There are several important differences:
|
|||
throw new Error("error");
|
||||
})
|
||||
.finally(() => alert("Promise ready"))
|
||||
.catch(err => alert(err)); // error
|
||||
.catch(err => alert(err)); // <-- .catch handles the error object
|
||||
```
|
||||
|
||||
That's very convenient, because finally is not meant to process promise results. So it passes them through.
|
||||
|
||||
We'll talk about promise chaining and passing around results in more detail in the next chapter.
|
||||
We'll talk more about promise chaining and result-passing between handlers in the next chapter.
|
||||
|
||||
3. The last, but not the least, `.finally(f)` is more convenient syntax than `.then(f, f)`: there's no need to duplicate a function.
|
||||
3. The last, but not the least, `.finally(f)` is a more convenient syntax than `.then(f, f)`: no need to duplicate the function.
|
||||
|
||||
````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:
|
||||
|
@ -269,33 +273,7 @@ let promise = new Promise(resolve => resolve("done!"));
|
|||
promise.then(alert); // done! (shows up right now)
|
||||
```
|
||||
|
||||
Some tasks may sometimes require time and sometimes finish immediately. The good thing is: the `.then` handler is guaranteed to run in both cases.
|
||||
````
|
||||
|
||||
````smart header="Handlers of `.then`/`.catch`/`.finally` are always asynchronous"
|
||||
Even when the Promise is immediately resolved, code which occurs on lines *below* your `.then`/`.catch`/`.finally` may still execute first.
|
||||
|
||||
The JavaScript engine has an internal execution queue which gets all `.then/catch/finally` handlers.
|
||||
|
||||
But it only looks into that queue when the current execution is finished. In other words, the handlers are pending execution until the engine is done with the current code.
|
||||
|
||||
For instance, here:
|
||||
|
||||
```js run
|
||||
// an "immediately" resolved Promise
|
||||
const executor = resolve => resolve("done!");
|
||||
const promise = new Promise(executor);
|
||||
|
||||
promise.then(alert); // this alert shows last (*)
|
||||
|
||||
alert("code finished"); // this alert shows first
|
||||
```
|
||||
|
||||
The promise becomes settled immediately, but the engine first finishes the current code, calls `alert("code finished")`, and only *afterwards* looks into the queue to run `.then` handler.
|
||||
|
||||
So the code *after* `.then` ends up always running *before* the Promise's subscribers, even in the case of an immediately-resolved Promise.
|
||||
|
||||
Sometimes that's unimportant, while in some scenarios the order may matter a great deal.
|
||||
The good thing is: `.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.
|
||||
|
|
109
1-js/11-async/05-promise-queue/article.md
Normal file
109
1-js/11-async/05-promise-queue/article.md
Normal file
|
@ -0,0 +1,109 @@
|
|||
|
||||
# Promise handlers queue
|
||||
|
||||
Promise handlers `.then`/`.catch`/`.finally` are always asynchronous.
|
||||
|
||||
Even when a Promise is immediately resolved, the code on the lines *below* your `.then`/`.catch`/`.finally` may still execute first.
|
||||
|
||||
Here's the code that demonstrates it:
|
||||
|
||||
```js run
|
||||
// an "immediately" resolved Promise
|
||||
new Promise(resolve => resolve("promise done!"))
|
||||
.then(alert); // this alert shows after the alert below
|
||||
|
||||
alert("code finished"); // this alert shows first
|
||||
```
|
||||
|
||||
If you run it, you see `code finished` first, and then `promise done!`.
|
||||
|
||||
What's going on?
|
||||
|
||||
# Internal queue
|
||||
|
||||
Asynchronous tasks need proper management. For that, the standard specifies an internal queue of "Promise Jobs".
|
||||
|
||||
As said in the [specification](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues):
|
||||
|
||||
- The queue is first-in-first-out: jobs that get enqueued first are run first.
|
||||
- Execution of a job is initiated only when there is no running execution context, and the execution context stack is empty.
|
||||
|
||||
When a promise is ready, its `.then/catch/finally` handler is not executed right ahead. Instead, the handling is put into the queue.
|
||||
|
||||
Javascript engine takes a job from the queue and executes it, when it finishes executing the current code.
|
||||
|
||||
That's why "code finished" in the example above shows first.
|
||||
|
||||

|
||||
|
||||
If there's a chain with multiple `.then/catch/finally`, then every one of them is executed asynchronously. In other words, it first gets queued, and then executed, when all handlers before it are finished.
|
||||
|
||||
What if the order matters for us, and we want to see `code finished` after `promise done`?
|
||||
|
||||
Easy, just put it into the queue:
|
||||
|
||||
```js run
|
||||
new Promise(resolve => resolve("promise done!"))
|
||||
.then(alert)
|
||||
.then(() => alert("code finished");
|
||||
```
|
||||
|
||||
Now the order is as intended.
|
||||
|
||||
## Higher-order queues
|
||||
|
||||
There are other action queues, depending on the environment.
|
||||
|
||||
For instance, `setTimeout` enqueues an action when the time comes, or even right now if the timeout is zero:
|
||||
|
||||
```js
|
||||
setTimeout(handler, 0); // handler is queued for immediate execution.
|
||||
```
|
||||
|
||||
Other examples:
|
||||
- Pending events (like mouse movements in the browser)
|
||||
- Network operations that may take time, or may finish immediately if the result is cached.
|
||||
|
||||
**Promise queue has higher priority than environment-related queues.**
|
||||
|
||||
For instance, take a look:
|
||||
|
||||
```js run
|
||||
setTimeout(() => alert("timeout"), 0);
|
||||
|
||||
new Promise(resolve => resolve("promise"))
|
||||
.then(alert);
|
||||
|
||||
alert("code");
|
||||
```
|
||||
|
||||
1. `code` shows first, it's a regular synchronous call.
|
||||
2. `promise` shows second, because `.then` passes through the promise queue, runs after the current code.
|
||||
3. `timeout` shows last, because environment-specific queue has lower priority.
|
||||
|
||||
That's also true for more complex calls, e.g if we schedule an immediate `setTimeout` call inside the promise, then it also executes last:
|
||||
|
||||
```js run
|
||||
new Promise(resolve => {
|
||||
setTimeout(() => alert("timeout"), 0);
|
||||
resolve("promise"); // shows up first
|
||||
}).then(alert);
|
||||
```
|
||||
|
||||
Here also `promise` triggers first, because promise actions have higher priority.
|
||||
|
||||
## Summary
|
||||
|
||||
Promise handling is always async, as all promise actions pass through the internal "promise jobs" queue.
|
||||
|
||||
**So, `.then/catch/finally` is called after the current code is finished.**
|
||||
|
||||
If we need to guarantee that a piece of code is executed after `.then/catch/finally`, it's best to add it into a chained `.then` call.
|
||||
|
||||
Other environments may have their own async actions, like events, network-related calls, filesystem tasks, and `setTimeout`-scheduled calls.
|
||||
|
||||
**Environment-specific async actions happen after the code is finished *and* after the promise queue is empty.**
|
||||
|
||||
In other words, they have lower priority.
|
||||
|
||||
So we know for sure a promise chain goes as far as possible first. It may finish or hang waiting for something outside of the promise queue, and only then an event-related handler or `setTimeout` may trigger.
|
BIN
1-js/11-async/05-promise-queue/promiseQueue.png
Normal file
BIN
1-js/11-async/05-promise-queue/promiseQueue.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
BIN
1-js/11-async/05-promise-queue/promiseQueue@2x.png
Normal file
BIN
1-js/11-async/05-promise-queue/promiseQueue@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
|
@ -281,6 +281,34 @@ In case of an error, it propagates as usual: from the failed promise to `Promise
|
|||
|
||||
````
|
||||
|
||||
## Async/await versus other async actions
|
||||
|
||||
Some async stuff is more asynchronous than the other.
|
||||
|
||||
For instance, `setTimeout(handler, 0)` is async, and `let x = await f()` is async. What triggers first?
|
||||
|
||||
```js run
|
||||
async function f() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
setTimeout(() => alert('timeout'), 0);
|
||||
|
||||
await f();
|
||||
|
||||
alert('await');
|
||||
})();
|
||||
```
|
||||
|
||||
There's no ambiguity here: `await` always finishes first.
|
||||
|
||||
Remember promise queue from the chapter <info:promise-queue>? Promise `.then/catch/finally` handlers get queued, and then executed when the currently running code is complete. By specification, the promise queue has higher priority than environment-specific handlers.
|
||||
|
||||
`Async/await` is based on promises, so it uses the same promise queue internally.
|
||||
|
||||
So `await` is guaranteed to work before any `setTimeout` or other event handlers. That's actually quite essential, as we know that our async/await will never be interrupted by other handlers or events.
|
||||
|
||||
## Summary
|
||||
|
||||
The `async` keyword before a function has two effects:
|
Loading…
Add table
Add a link
Reference in a new issue