This commit is contained in:
Ilya Kantor 2019-03-13 23:08:35 +03:00
parent e60bb12707
commit 5ca83ba7ba
24 changed files with 144 additions and 137 deletions

View file

@ -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.
![](promiseQueue.png)
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

View 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.
![](promiseQueue.png)
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).
![](eventLoop.png)
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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -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:

Binary file not shown.