event-loop
This commit is contained in:
parent
d1190aae21
commit
f018012168
12 changed files with 378 additions and 305 deletions
|
@ -1,5 +1,5 @@
|
|||
|
||||
# Microtasks and event loop
|
||||
# Microtasks
|
||||
|
||||
Promise handlers `.then`/`.catch`/`.finally` are always asynchronous.
|
||||
|
||||
|
@ -52,99 +52,15 @@ Promise.resolve()
|
|||
|
||||
Now the order is as intended.
|
||||
|
||||
## Event loop
|
||||
|
||||
In-browser JavaScript execution flow, as well as Node.js, is based on an *event loop*.
|
||||
|
||||
"Event loop" is a process when the engine sleeps and waits for events. When they occur - handles them and sleeps again.
|
||||
|
||||
Events may come either from external sources, like user actions, or just as the end signal of an internal task.
|
||||
|
||||
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).
|
||||
|
||||

|
||||
|
||||
As you can see, there's also a queue here. A so-called "macrotask queue" (v8 term).
|
||||
|
||||
When an event happens, while the engine is busy, its handling is enqueued.
|
||||
|
||||
For instance, while the engine is busy processing a network `fetch`, a user may move their mouse causing `mousemove`, and `setTimeout` may be due and so on, just as painted on the picture above.
|
||||
|
||||
Events from the macrotask queue are processed on "first come – 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"));
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => alert("promise"));
|
||||
|
||||
alert("code");
|
||||
```
|
||||
|
||||
What's the order?
|
||||
|
||||
1. `code` shows first, because it's a regular synchronous call.
|
||||
2. `promise` shows second, because `.then` passes through the microtask queue, and 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, vice-versa, a microtask schedules a macrotask (e.g. `setTimeout`).
|
||||
|
||||
For instance, here `.then` schedules a `setTimeout`:
|
||||
|
||||
```js run
|
||||
Promise.resolve()
|
||||
.then(() => {
|
||||
setTimeout(() => alert("timeout"), 0);
|
||||
})
|
||||
.then(() => {
|
||||
alert("promise");
|
||||
});
|
||||
```
|
||||
|
||||
Naturally, `promise` shows up first, because `setTimeout` macrotask awaits in the less-priority macrotask queue.
|
||||
|
||||
As a logical consequence, macrotasks are handled only when promises give the engine a "free time". So if we have a chain of promise handlers that don't wait for anything, execute right one after another, then a `setTimeout` (or a user action handler) can never run in-between them.
|
||||
|
||||
## Unhandled rejection
|
||||
|
||||
Remember "unhandled rejection" event from the chapter <info:promise-error-handling>?
|
||||
|
||||
Now we can describe how JavaScript finds out that a rejection was not handled.
|
||||
Now we can see exactly how JavaScript finds out that there was an unhandled rejection
|
||||
|
||||
**"Unhandled rejection" is when a promise error is not handled at the end of the microtask queue.**
|
||||
**"Unhandled rejection" occurs when a promise error is not handled at the end of the microtask queue.**
|
||||
|
||||
For instance, consider this code:
|
||||
|
||||
```js run
|
||||
let promise = Promise.reject(new Error("Promise Failed!"));
|
||||
|
||||
window.addEventListener('unhandledrejection', event => {
|
||||
alert(event.reason); // Promise Failed!
|
||||
});
|
||||
```
|
||||
|
||||
We create a rejected `promise` and do not handle the error. So we have the "unhandled rejection" event (printed in browser console too).
|
||||
|
||||
We wouldn't have it if we added `.catch`, like this:
|
||||
Normally, if we expect an error, we add `.catch` to the promise chain to handle it:
|
||||
|
||||
```js run
|
||||
let promise = Promise.reject(new Error("Promise Failed!"));
|
||||
|
@ -156,36 +72,41 @@ promise.catch(err => alert('caught'));
|
|||
window.addEventListener('unhandledrejection', event => alert(event.reason));
|
||||
```
|
||||
|
||||
Now let's say, we'll be catching the error, but after `setTimeout`:
|
||||
...But if we forget to add `.catch`, then, after the microtask queue is empty, the engine triggers the event:
|
||||
|
||||
```js run
|
||||
let promise = Promise.reject(new Error("Promise Failed!"));
|
||||
|
||||
// Promise Failed!
|
||||
window.addEventListener('unhandledrejection', event => alert(event.reason));
|
||||
```
|
||||
|
||||
What if we handle the error later? Like this:
|
||||
|
||||
```js run
|
||||
let promise = Promise.reject(new Error("Promise Failed!"));
|
||||
*!*
|
||||
setTimeout(() => promise.catch(err => alert('caught')));
|
||||
setTimeout(() => promise.catch(err => alert('caught')), 1000);
|
||||
*/!*
|
||||
|
||||
// Error: Promise Failed!
|
||||
window.addEventListener('unhandledrejection', event => alert(event.reason));
|
||||
```
|
||||
|
||||
Now the unhandled rejection appears again. Why? Because `unhandledrejection` is generated when the microtask queue is complete. The engine examines promises and, if any of them is in "rejected" state, then the event triggers.
|
||||
Now, if you run it, we'll see `Promise Failed!` message first, and then `caught`.
|
||||
|
||||
In the example, the `.catch` added by `setTimeout` triggers too, of course it does, but later, after `unhandledrejection` has already occurred.
|
||||
If we didn't know about microtasks, we could wonder: "Why did `unhandledrejection` happen? We did catch the error!".
|
||||
|
||||
But now we do know that `unhandledrejection` is generated when the microtask queue is complete: the engine examines promises and, if any of them is in "rejected" state, then the event triggers.
|
||||
|
||||
...By the way, the `.catch` added by `setTimeout` also triggers, of course it does, but later, after `unhandledrejection` has already occurred.
|
||||
|
||||
## Summary
|
||||
|
||||
- Promise handling is always asynchronous, as all promise actions pass through the internal "promise jobs" queue, also called "microtask queue" (v8 term).
|
||||
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` handlers are called after the current code is finished.**
|
||||
So, `.then/catch/finally` handlers are always 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.
|
||||
If we need to guarantee that a piece of code is executed after `.then/catch/finally`, we can 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, like events etc.
|
||||
In most Javascript engines, including browsers and Node.js, the concept of microtasks is closely tied with "event loop" and "macrotasks". As these have no direct relation to promises, they are covered in another part of the tutorial, in the chapter <info:event-loop>.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue