This commit is contained in:
Ilya Kantor 2019-04-05 09:17:26 +03:00
parent d7d1c17de9
commit 2a39ef8050
2 changed files with 27 additions and 22 deletions

View file

@ -38,11 +38,9 @@ That's why "code finished" in the example above shows first.
Promise handlers always go through that internal queue. 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. 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.
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`?**
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`: Easy, just put it into the queue with `.then`:
@ -56,7 +54,7 @@ Now the order is as intended.
## Event loop ## Event loop
Browser Javascript, as well as Node.js, is based on an *event loop*. In-browser Javascript, as well as Node.js, is based on an *event loop*.
"Event loop" is a process when the engine sleeps and waits for events, then reacts on those and sleeps again. "Event loop" is a process when the engine sleeps and waits for events, then reacts on those and sleeps again.
@ -73,7 +71,7 @@ Things happen -- the engine handles them -- and waits for more to happen (while
As you can see, there's also a queue here. A so-called "macrotask queue" (v8 term). As you can see, there's also a queue here. A so-called "macrotask queue" (v8 term).
When an event happens, and the engine is busy, the event is enqueued. 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. 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.
@ -90,7 +88,7 @@ In other words, the engine first executes all microtasks, and then takes a macro
For instance, take a look: For instance, take a look:
```js run ```js run
setTimeout(() => alert("timeout"), 0); setTimeout(() => alert("timeout"));
Promise.resolve() Promise.resolve()
.then(() => alert("promise")); .then(() => alert("promise"));
@ -122,9 +120,7 @@ Promise.resolve()
Naturally, `promise` shows up first, because `setTimeout` macrotask awaits in the less-priority macrotask queue. Naturally, `promise` shows up first, because `setTimeout` macrotask awaits in the less-priority macrotask queue.
**As a side effect, macrotasks are handled only when promises give the engine a "free time".** As a logical consequence, macrotasks are handled only when promises give the engine a "free time". So if we have a promise chain that doesn't wait for anything, then things like `setTimeout` or event handlers can never get in the middle.
So call have a promise chain that doesn't wait for anything, then things like `setTimeout` or event handlers can never get in the middle.
## Unhandled rejection ## Unhandled rejection
@ -159,12 +155,12 @@ promise.catch(err => alert('caught'));
window.addEventListener('unhandledrejection', event => alert(event.reason)); window.addEventListener('unhandledrejection', event => alert(event.reason));
``` ```
Now let's say, we'll be catching the error, but after an extremely small delay: Now let's say, we'll be catching the error, but after `setTimeout`:
```js run ```js run
let promise = Promise.reject(new Error("Promise Failed!")); let promise = Promise.reject(new Error("Promise Failed!"));
*!* *!*
setTimeout(() => promise.catch(err => alert('caught')), 0); setTimeout(() => promise.catch(err => alert('caught')));
*/!* */!*
// Error: Promise Failed! // Error: Promise Failed!
@ -173,7 +169,7 @@ window.addEventListener('unhandledrejection', event => alert(event.reason));
Now the unhandled rejction appears again. Why? Because `unhandledrejection` triggers when the microtask queue is complete. The engine examines promises and, if any of them is in "rejected" state, then the event is generated. Now the unhandled rejction appears again. Why? Because `unhandledrejection` triggers when the microtask queue is complete. The engine examines promises and, if any of them is in "rejected" state, then the event is generated.
In the example above `setTimeout` adds the `.catch`, and it triggers too, of course it does, but later, after the event has already occured. In the example, the `.catch` added by `setTimeout` triggers too, of course it does, but later, after `unhandledrejection` has already occured.
## Summary ## Summary

View file

@ -130,19 +130,30 @@ let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json(); let user = await response.json();
``` ```
So we need to have a wrapping async function for the code that awaits. Just as in the example above. We can wrap it into an anonymous async function, like this:
````
````smart header="`await` accepts thenables" ```js run
Like `promise.then`, `await` allows to use thenable objects (those with a callable `then` method). Again, the idea is that a 3rd-party object may not be a promise, but promise-compatible: if it supports `.then`, that's enough to use with `await`. (async () => {
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
...
})();
```
````
````smart header="`await` accepts \"thenables\""
Like `promise.then`, `await` allows to use thenable objects (those with a callable `then` method). The idea is that a 3rd-party object may not be a promise, but promise-compatible: if it supports `.then`, that's enough to use with `await`.
Here's a demo `Thenable` class, the `await` below accepts its instances:
For instance, here `await` accepts `new Thenable(1)`:
```js run ```js run
class Thenable { class Thenable {
constructor(num) { constructor(num) {
this.num = num; this.num = num;
} }
then(resolve, reject) { then(resolve, reject) {
alert(resolve); // function() { native code } alert(resolve);
// resolve with this.num*2 after 1000ms // resolve with this.num*2 after 1000ms
setTimeout(() => resolve(this.num * 2), 1000); // (*) setTimeout(() => resolve(this.num * 2), 1000); // (*)
} }
@ -161,9 +172,7 @@ If `await` gets a non-promise object with `.then`, it calls that method providin
```` ````
````smart header="Async methods" ````smart header="Async methods"
A class method can also be async, just put `async` before it. To declare an async class method, just prepend it with `async`:
Like here:
```js run ```js run
class Waiter { class Waiter {