refactor
This commit is contained in:
parent
e60bb12707
commit
5ca83ba7ba
24 changed files with 144 additions and 137 deletions
|
@ -1,109 +0,0 @@
|
||||||
|
|
||||||
# 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", sometimes called "microtask queue" (v8 term).
|
|
||||||
|
|
||||||
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 with `.then`:
|
|
||||||
|
|
||||||
```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, often called "macrotasks" (as opposed to promise "microtasks").
|
|
||||||
|
|
||||||
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:
|
|
||||||
- Events that wait to be handled (like mouse moves in the browser)
|
|
||||||
- Completed network operations, pending to be handled. These, just like `setTimeout`, may take time, or finish immediately if the result is cached.
|
|
||||||
|
|
||||||
**Promise queue has a 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 nested calls, e.g if we schedule an immediate `setTimeout` call inside the promise:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
new Promise(resolve => {
|
|
||||||
setTimeout(() => alert("timeout"), 0);
|
|
||||||
resolve("promise"); // shows up first
|
|
||||||
}).then(alert);
|
|
||||||
```
|
|
||||||
|
|
||||||
Here also `promise` triggers first, because promise handling have higher priority.
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Promise handling is always async, as all promise actions pass through the internal "promise jobs" queue, also called "microtask queue" (v8 term).
|
|
||||||
|
|
||||||
**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. These are also called "macrotasks" (v8 term).
|
|
||||||
|
|
||||||
**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 the order is: regular code, then promise handling, then everything else.
|
|
Binary file not shown.
Before Width: | Height: | Size: 23 KiB |
Binary file not shown.
Before Width: | Height: | Size: 56 KiB |
139
1-js/11-async/07-microtask-queue/article.md
Normal file
139
1-js/11-async/07-microtask-queue/article.md
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
|
||||||
|
# Microtasks, event loop
|
||||||
|
|
||||||
|
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
|
||||||
|
let promise = Promise.resolve();
|
||||||
|
|
||||||
|
promise.then(() => alert("promise done"));
|
||||||
|
|
||||||
|
alert("code finished"); // this alert shows first
|
||||||
|
```
|
||||||
|
|
||||||
|
If you run it, you see `code finished` first, and then `promise done`.
|
||||||
|
|
||||||
|
That's strange, because the promise is definitely done from the beginning.
|
||||||
|
|
||||||
|
Why `.then` triggered after? What's going on?
|
||||||
|
|
||||||
|
# Microtasks
|
||||||
|
|
||||||
|
Asynchronous tasks need proper management. For that, the standard specifies an internal queue `PromiseJobs`, more often called the "microtask queue" (v8 term).
|
||||||
|
|
||||||
|
As said in the [specification](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues):
|
||||||
|
|
||||||
|
- The queue is first-in-first-out: tasks that get enqueued first are run first.
|
||||||
|
- Execution of a task is initiated only when nothing else is running.
|
||||||
|
|
||||||
|
Or, to say that simply, when a promise is ready, its `.then/catch/finally` handlers are put into the queue. They are not executed yet. Javascript engine takes a task from the queue and executes it, when it becomes free from the current code.
|
||||||
|
|
||||||
|
That's why "code finished" in the example above shows first.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Promise handlers always go through that internal queue.
|
||||||
|
|
||||||
|
If there's a chain with multiple `.then/catch/finally`, then every one of them is executed asynchronously.
|
||||||
|
|
||||||
|
That is, it first gets queued, and executed when the current code is complete and previously queued handlers are finished.
|
||||||
|
|
||||||
|
What if the order matters for us? How to make `code finished` work after `promise done`?
|
||||||
|
|
||||||
|
Easy, just put it into the queue with `.then`:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
Promise.resolve()
|
||||||
|
.then(() => alert("promise done!"))
|
||||||
|
.then(() => alert("code finished"));
|
||||||
|
```
|
||||||
|
|
||||||
|
Now the order is as intended.
|
||||||
|
|
||||||
|
## Event loop
|
||||||
|
|
||||||
|
Browser Javascript, as well as Node.js, is based on an *event loop*.
|
||||||
|
|
||||||
|
"Event loop" is a process when the engine sleeps waits for events.
|
||||||
|
|
||||||
|
Examples of events:
|
||||||
|
- `mousemove`, a user moved their mouse.
|
||||||
|
- `setTimeout` handler is to be called.
|
||||||
|
- an external `<script src="...">` is loaded, ready to be executed.
|
||||||
|
- a network operation, e.g. `fetch` is complete.
|
||||||
|
- ...etc.
|
||||||
|
|
||||||
|
Things happen -- the engine handles them -- and waits for more to happen (while sleeping and consuming close to zero CPU).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
When an event happens, and the engine is busy, it gets into a so-called "macrotask queue" (v8 term).
|
||||||
|
|
||||||
|
For instance, while the engine is busy processing a network `fetch`, a user may move their mouse causing `mousemove`, and `setTimeout` is due and so on, just as painted on the picture above.
|
||||||
|
|
||||||
|
Events from the macrotask queue are processed on "first came – first served" basis. When the engine browser finishes with `fetch`, it handles `mousemove` event, then `setTimeout` handler, and so on.
|
||||||
|
|
||||||
|
So far, quite simple, right? The engine is busy, so other tasks queue up.
|
||||||
|
|
||||||
|
Now the important stuff.
|
||||||
|
|
||||||
|
**Microtask queue has a higher priority than the macrotask queue.**
|
||||||
|
|
||||||
|
In other words, the engine first executes all microtasks, and then takes a macrotask. Promise handling always has the priority.
|
||||||
|
|
||||||
|
For instance, take a look:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
setTimeout(() => alert("timeout"), 0);
|
||||||
|
|
||||||
|
Promise.resolved()
|
||||||
|
.then(() => alert("promise"));
|
||||||
|
|
||||||
|
alert("code");
|
||||||
|
```
|
||||||
|
|
||||||
|
What's the order?
|
||||||
|
|
||||||
|
1. `code` shows first, it's a regular synchronous call.
|
||||||
|
2. `promise` shows second, because `.then` passes through the microtask queue, runs after the current code.
|
||||||
|
3. `timeout` shows last, because it's a macrotask.
|
||||||
|
|
||||||
|
It may happen that while handling a macrotask, new promises are created.
|
||||||
|
|
||||||
|
Or, vise-versa, a microtask schedules a macrotask (e.g. `setTimeout`).
|
||||||
|
|
||||||
|
For instance, here `.then` schedules a `setTimeout`:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
Promise.resolved()
|
||||||
|
.then(() => {
|
||||||
|
setTimeout(() => alert("timeout"), 0);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
alert("promise");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Naturally, `promise` shows up first, because `setTimeout` macrotask awaits in the less-priority macrotask queue.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Promise handling is always asynchronous, as all promise actions pass through the internal "promise jobs" queue, also called "microtask queue" (v8 term).
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
There's also a "macrotask queue" that keeps various events, network operation results, `setTimeout`-scheduled calls, and so on. These are also called "macrotasks" (v8 term).
|
||||||
|
|
||||||
|
The engine uses the macrotask queue to handle them in the appearance order.
|
||||||
|
|
||||||
|
**Macrotasks run after the code is finished *and* after the microtask queue is empty.**
|
||||||
|
|
||||||
|
In other words, they have lower priority.
|
||||||
|
|
||||||
|
So the order is: regular code, then promise handling, then everything else.
|
BIN
1-js/11-async/07-microtask-queue/eventLoop.png
Normal file
BIN
1-js/11-async/07-microtask-queue/eventLoop.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
1-js/11-async/07-microtask-queue/eventLoop@2x.png
Normal file
BIN
1-js/11-async/07-microtask-queue/eventLoop@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
BIN
1-js/11-async/07-microtask-queue/promiseQueue.png
Normal file
BIN
1-js/11-async/07-microtask-queue/promiseQueue.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
1-js/11-async/07-microtask-queue/promiseQueue@2x.png
Normal file
BIN
1-js/11-async/07-microtask-queue/promiseQueue@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
|
@ -281,25 +281,11 @@ In case of an error, it propagates as usual: from the failed promise to `Promise
|
||||||
|
|
||||||
````
|
````
|
||||||
|
|
||||||
## Microtask queue: await and higher-level actions [#microtask-queue]
|
## Microtask queue [#microtask-queue]
|
||||||
|
|
||||||
TODO
|
As we've seen in the chapter <info:microtask-queue>, promise handlers are executed asynchronously. Every `.then/catch/finally` handler first gets into the "microtask queue" and executed after the current code is complete.
|
||||||
|
|
||||||
|
`Async/await` is based on promises, so it uses the same microtask queue internally, and has the similar priority over macrotasks.
|
||||||
In the browser, or other environments we have two types of async actions:
|
|
||||||
1. `async/await`
|
|
||||||
2. `setTimeout` calls that are to execute, pending events
|
|
||||||
|
|
||||||
|
|
||||||
`Async/await` is based on promises, so it uses the promise queue internally, so-called "microtask queue".
|
|
||||||
|
|
||||||
When a promise is ready, it's handling is put to a special internal queue, and processed when Javascript has finished with the current code.
|
|
||||||
|
|
||||||
That's explained in detail in the chapter <info:promise-queue>. Promise `.then/catch/finally` handlers get queued, and then executed when the currently running code is complete.
|
|
||||||
|
|
||||||
If we use `async/await`, things are much simpler than with promises. No chaining. So we don't care much about that internal queue.
|
|
||||||
|
|
||||||
But there's an important side effect that we should understand: some async stuff is more asynchronous than the other.
|
|
||||||
|
|
||||||
For instance, we have:
|
For instance, we have:
|
||||||
- `setTimeout(handler, 0)`, that should run `handler` with zero delay.
|
- `setTimeout(handler, 0)`, that should run `handler` with zero delay.
|
||||||
|
@ -313,25 +299,16 @@ async function f() {
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
setTimeout(() => alert('timeout finished'), 0);
|
setTimeout(() => alert('timeout'), 0);
|
||||||
|
|
||||||
await f();
|
await f();
|
||||||
|
|
||||||
alert('await finished');
|
alert('await');
|
||||||
})();
|
})();
|
||||||
```
|
```
|
||||||
|
|
||||||
There's no ambiguity here: `await` always finishes first.
|
There's no ambiguity here: `await` always finishes first.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Internally, there`setTimeout` and event handlers are also processed in a queue
|
|
||||||
|
|
||||||
|
|
||||||
By specification, the promise queue has higher priority than environment-specific handlers.
|
|
||||||
|
|
||||||
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 code flow will never be interrupted by other handlers or events.
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
The `async` keyword before a function has two effects:
|
The `async` keyword before a function has two effects:
|
||||||
|
|
BIN
figures.sketch
BIN
figures.sketch
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue