refactor classes, add private, minor fixes

This commit is contained in:
Ilya Kantor 2019-03-05 18:44:28 +03:00
parent a0c07342ad
commit 1373f6158c
270 changed files with 1513 additions and 890 deletions

View file

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.message-ball {
font-size: 20px;
line-height: 200px;
text-align: center;
}
.circle {
transition-property: width, height, margin-left, margin-top;
transition-duration: 2s;
position: fixed;
transform: translateX(-50%) translateY(-50%);
background-color: red;
border-radius: 50%;
}
</style>
</head>
<body>
<button onclick="go()">Click me</button>
<script>
function go() {
showCircle(150, 150, 100, div => {
div.classList.add('message-ball');
div.append("Hello, world!");
});
}
function showCircle(cx, cy, radius, callback) {
let div = document.createElement('div');
div.style.width = 0;
div.style.height = 0;
div.style.left = cx + 'px';
div.style.top = cy + 'px';
div.className = 'circle';
document.body.append(div);
setTimeout(() => {
div.style.width = radius * 2 + 'px';
div.style.height = radius * 2 + 'px';
div.addEventListener('transitionend', function handler() {
div.removeEventListener('transitionend', handler);
callback(div);
});
}, 0);
}
</script>
</body>
</html>

View file

@ -0,0 +1,25 @@
# Animated circle with callback
In the task <info:task/animate-circle> an animated growing circle is shown.
Now let's say we need not just a circle, but to show a message inside it. The message should appear *after* the animation is complete (the circle is fully grown), otherwise it would look ugly.
In the solution of the task, the function `showCircle(cx, cy, radius)` draws the circle, but gives no way to track when it's ready.
Add a callback argument: `showCircle(cx, cy, radius, callback)` to be called when the animation is complete. The `callback` should receive the circle `<div>` as an argument.
Here's the example:
```js
showCircle(150, 150, 100, div => {
div.classList.add('message-ball');
div.append("Hello, world!");
});
```
Demo:
[iframe src="solution" height=260]
Take the solution of the task <info:task/animate-circle> as the base.

View file

@ -0,0 +1,271 @@
# Introduction: callbacks
Many actions in JavaScript are *asynchronous*.
For instance, take a look at the function `loadScript(src)`:
```js
function loadScript(src) {
let script = document.createElement('script');
script.src = src;
document.head.append(script);
}
```
The purpose of the function is to load a new script. When it adds the `<script src="…">` to the document, the browser loads and executes it.
We can use it like this:
```js
// loads and executes the script
loadScript('/my/script.js');
```
The function is called "asynchronously," because the action (script loading) finishes not now, but later.
The call initiates the script loading, then the execution continues. While the script is loading, the code below may finish executing, and if the loading takes time, other scripts may run meanwhile too.
```js
loadScript('/my/script.js');
// the code below loadScript doesn't wait for the script loading to finish
// ...
```
Now let's say we want to use the new script when it loads. It probably declares new functions, so we'd like to run them.
But if we do that immediately after the `loadScript(…)` call, that wouldn't work:
```js
loadScript('/my/script.js'); // the script has "function newFunction() {…}"
*!*
newFunction(); // no such function!
*/!*
```
Naturally, the browser probably didn't have time to load the script. So the immediate call to the new function fails. As of now, the `loadScript` function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when it happens, to use new functions and variables from that script.
Let's add a `callback` function as a second argument to `loadScript` that should execute when the script loads:
```js
function loadScript(src, *!*callback*/!*) {
let script = document.createElement('script');
script.src = src;
*!*
script.onload = () => callback(script);
*/!*
document.head.append(script);
}
```
Now if we want to call new functions from the script, we should write that in the callback:
```js
loadScript('/my/script.js', function() {
// the callback runs after the script is loaded
newFunction(); // so now it works
...
});
```
That's the idea: the second argument is a function (usually anonymous) that runs when the action is completed.
Here's a runnable example with a real script:
```js run
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}
*!*
loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => {
alert(`Cool, the ${script.src} is loaded`);
alert( _ ); // function declared in the loaded script
});
*/!*
```
That's called a "callback-based" style of asynchronous programming. A function that does something asynchronously should provide a `callback` argument where we put the function to run after it's complete.
Here we did it in `loadScript`, but of course, it's a general approach.
## Callback in callback
How to load two scripts sequentially: the first one, and then the second one after it?
The natural solution would be to put the second `loadScript` call inside the callback, like this:
```js
loadScript('/my/script.js', function(script) {
alert(`Cool, the ${script.src} is loaded, let's load one more`);
*!*
loadScript('/my/script2.js', function(script) {
alert(`Cool, the second script is loaded`);
});
*/!*
});
```
After the outer `loadScript` is complete, the callback initiates the inner one.
What if we want one more script...?
```js
loadScript('/my/script.js', function(script) {
loadScript('/my/script2.js', function(script) {
*!*
loadScript('/my/script3.js', function(script) {
// ...continue after all scripts are loaded
});
*/!*
})
});
```
So, every new action is inside a callback. That's fine for few actions, but not good for many, so we'll see other variants soon.
## Handling errors
In examples above we didn't consider errors. What if the script loading fails? Our callback should be able to react on that.
Here's an improved version of `loadScript` that tracks loading errors:
```js run
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
*!*
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`Script load error for ${src}`));
*/!*
document.head.append(script);
}
```
It calls `callback(null, script)` for successful load and `callback(error)` otherwise.
The usage:
```js
loadScript('/my/script.js', function(error, script) {
if (error) {
// handle error
} else {
// script loaded successfully
}
});
```
Once again, the recipe that we used for `loadScript` is actually quite common. It's called the "error-first callback" style.
The convention is:
1. The first argument of the `callback` is reserved for an error if it occurs. Then `callback(err)` is called.
2. The second argument (and the next ones if needed) are for the successful result. Then `callback(null, result1, result2…)` is called.
So the single `callback` function is used both for reporting errors and passing back results.
## Pyramid of Doom
From the first look, it's a viable way of asynchronous coding. And indeed it is. For one or maybe two nested calls it looks fine.
But for multiple asynchronous actions that follow one after another we'll have code like this:
```js
loadScript('1.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', function(error, script) {
if (error) {
handleError(error);
} else {
*!*
// ...continue after all scripts are loaded (*)
*/!*
}
});
}
})
}
});
```
In the code above:
1. We load `1.js`, then if there's no error.
2. We load `2.js`, then if there's no error.
3. We load `3.js`, then if there's no error -- do something else `(*)`.
As calls become more nested, the code becomes deeper and increasingly more difficult to manage, especially if we have a real code instead of `...`, that may include more loops, conditional statements and so on.
That's sometimes called "callback hell" or "pyramid of doom."
![](callback-hell.png)
The "pyramid" of nested calls grows to the right with every asynchronous action. Soon it spirals out of control.
So this way of coding isn't very good.
We can try to alleviate the problem by making every action a standalone function, like this:
```js
loadScript('1.js', step1);
function step1(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', step2);
}
}
function step2(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', step3);
}
}
function step3(error, script) {
if (error) {
handleError(error);
} else {
// ...continue after all scripts are loaded (*)
}
};
```
See? It does the same, and there's no deep nesting now because we made every action a separate top-level function.
It works, but the code looks like a torn apart spreadsheet. It's difficult to read, and you probably noticed that. One needs to eye-jump between pieces while reading it. That's inconvenient, especially if the reader is not familiar with the code and doesn't know where to eye-jump.
Also, the functions named `step*` are all of single use, they are created only to avoid the "pyramid of doom." No one is going to reuse them outside of the action chain. So there's a bit of a namespace cluttering here.
We'd like to have something better.
Luckily, there are other ways to avoid such pyramids. One of the best ways is to use "promises," described in the next chapter.

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View file

@ -0,0 +1,3 @@
function one() {
alert(1);
}

View file

@ -0,0 +1,3 @@
The output is: `1`.
The second call to `resolve` is ignored, because only the first call of `reject/resolve` is taken into account. Further calls are ignored.

View file

@ -0,0 +1,15 @@
# Re-resolve a promise?
What's the output of the code below?
```js
let promise = new Promise(function(resolve, reject) {
resolve(1);
setTimeout(() => resolve(2), 1000);
});
promise.then(alert);
```

View file

@ -0,0 +1,9 @@
```js run
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
delay(3000).then(() => alert('runs after 3 seconds'));
```
Please note that in this task `resolve` is called without arguments. We don't return any value from `delay`, just ensure the delay.

View file

@ -0,0 +1,14 @@
# Delay with a promise
The built-in function `setTimeout` uses callbacks. Create a promise-based alternative.
The function `delay(ms)` should return a promise. That promise should resolve after `ms` milliseconds, so that we can add `.then` to it, like this:
```js
function delay(ms) {
// your code
}
delay(3000).then(() => alert('runs after 3 seconds'));
```

View file

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.message-ball {
font-size: 20px;
line-height: 200px;
text-align: center;
}
.circle {
transition-property: width, height, margin-left, margin-top;
transition-duration: 2s;
position: fixed;
transform: translateX(-50%) translateY(-50%);
background-color: red;
border-radius: 50%;
}
</style>
</head>
<body>
<button onclick="go()">Click me</button>
<script>
function go() {
showCircle(150, 150, 100).then(div => {
div.classList.add('message-ball');
div.append("Hello, world!");
});
}
function showCircle(cx, cy, radius) {
let div = document.createElement('div');
div.style.width = 0;
div.style.height = 0;
div.style.left = cx + 'px';
div.style.top = cy + 'px';
div.className = 'circle';
document.body.append(div);
return new Promise(resolve => {
setTimeout(() => {
div.style.width = radius * 2 + 'px';
div.style.height = radius * 2 + 'px';
div.addEventListener('transitionend', function handler() {
div.removeEventListener('transitionend', handler);
resolve(div);
});
}, 0);
})
}
</script>
</body>
</html>

View file

@ -0,0 +1,15 @@
# Animated circle with promise
Rewrite the `showCircle` function in the solution of the task <info:task/animate-circle-callback> so that it returns a promise instead of accepting a callback.
The new usage:
```js
showCircle(150, 150, 100).then(div => {
div.classList.add('message-ball');
div.append("Hello, world!");
});
```
Take the solution of the task <info:task/animate-circle-callback> as the base.

View file

@ -0,0 +1,360 @@
# Promise
Imagine that you're a top singer, and fans ask day and night for your upcoming single.
To get some relief, you promise to send it to them when it's published. You give your fans a list to which they can subscribe for updates. They can fill in their email addresses, so that when the song becomes available, all subscribed parties instantly receive it. And even if something goes very wrong, say, if plans to publish the song are cancelled, they will still be notified.
Everyone is happy, because the people don't crowd you any more, and fans, because they won't miss the single.
This is a real-life analogy for things we often have in programming:
1. A "producing code" that does something and takes time. For instance, the code loads a remote script. That's a "singer".
2. A "consuming code" that wants the result of the "producing code" once it's ready. Many functions may need that result. These are the "fans".
3. A *promise* is a special JavaScript object that links the "producing code" and the "consuming code" together. In terms of our analogy: this is the "subscription list". The "producing code" takes whatever time it needs to produce the promised result, and the "promise" makes that result available to all of the subscribed code when it's ready.
The analogy isn't terribly accurate, because JavaScript promises are more complex than a simple subscription list: they have additional features and limitations. But it's fine to begin with.
The constructor syntax for a promise object is:
```js
let promise = new Promise(function(resolve, reject) {
// executor (the producing code, "singer")
});
```
The function passed to `new Promise` is called the *executor*. When the promise is created, this executor function runs automatically. It contains the producing code, that should eventually produce a result. In terms of the analogy above: the executor is the "singer".
The resulting `promise` object has internal properties:
- `state` — initially "pending", then changes to either "fulfilled" or "rejected",
- `result` — an arbitrary value of your choosing, initially `undefined`.
When the executor finishes the job, it should call one of the functions that it gets as arguments:
- `resolve(value)` — to indicate that the job finished successfully:
- sets `state` to `"fulfilled"`,
- sets `result` to `value`.
- `reject(error)` — to indicate that an error occurred:
- sets `state` to `"rejected"`,
- sets `result` to `error`.
![](promise-resolve-reject.png)
Later we'll see how these changes become known to "fans".
Here's an example of a Promise constructor and a simple executor function with its "producing code" (the `setTimeout`):
```js run
let promise = new Promise(function(resolve, reject) {
// the function is executed automatically when the promise is constructed
// after 1 second signal that the job is done with the result "done!"
setTimeout(() => *!*resolve("done!")*/!*, 1000);
});
```
We can see two things by running the code above:
1. The executor is called automatically and immediately (by the `new Promise`).
2. The executor receives two arguments: `resolve` and `reject` — these functions are pre-defined by the JavaScript engine. So we don't need to create them. Instead, we should write the executor to call them when ready.
After one second of "processing" the executor calls `resolve("done")` to produce the result:
![](promise-resolve-1.png)
That was an example of a successful job completion, a "fulfilled promise".
And now an example of the executor rejecting the promise with an error:
```js
let promise = new Promise(function(resolve, reject) {
// after 1 second signal that the job is finished with an error
setTimeout(() => *!*reject(new Error("Whoops!"))*/!*, 1000);
});
```
![](promise-reject-1.png)
To summarize, the executor should do a job (something that takes time usually) and then call `resolve` or `reject` to change the state of the corresponding Promise object.
The Promise that is either resolved or rejected is called "settled", as opposed to a "pending" Promise.
````smart header="There can be only a single result or an error"
The executor should call only one `resolve` or one `reject`. The promise's state change is final.
All further calls of `resolve` and `reject` are ignored:
```js
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // ignored
setTimeout(() => resolve("…")); // ignored
});
```
The idea is that a job done by the executor may have only one result or an error.
Also, `resolve`/`reject` expect only one argument (or none) and will ignore additional arguments.
````
```smart header="Reject with `Error` objects"
In case something goes wrong, we can call `reject` with any type of argument (just like `resolve`). But it is recommended to use `Error` objects (or objects that inherit from `Error`). The reasoning for that will soon become apparent.
```
````smart header="Immediately calling `resolve`/`reject`"
In practice, an executor usually does something asynchronously and calls `resolve`/`reject` after some time, but it doesn't have to. We also can call `resolve` or `reject` immediately, like this:
```js
let promise = new Promise(function(resolve, reject) {
// not taking our time to do the job
resolve(123); // immediately give the result: 123
});
```
For instance, this might happen when we start to do a job but then see that everything has already been completed.
That's fine. We immediately have a resolved Promise, nothing wrong with that.
````
```smart header="The `state` and `result` are internal"
The properties `state` and `result` of the Promise object are internal. We can't directly access them from our "consuming code". We can use the methods `.then`/`.catch`/`.finally` for that. They are described below.
```
## Consumers: then, catch, finally
A Promise object serves as a link between the executor (the "producing code" or "singer") and the consuming functions (the "fans"), which will receive the result or error. Consuming functions can be registered (subscribed) using methods `.then`, `.catch` and `.finally`.
### then
The most important, fundamental one is `.then`.
The syntax is:
```js
promise.then(
function(result) { *!*/* handle a successful result */*/!* },
function(error) { *!*/* handle an error */*/!* }
);
```
The first argument of `.then` is a function that:
1. runs when the Promise is resolved, and
2. receives the result.
The second argument of `.then` is a function that:
1. runs when the Promise is rejected, and
2. receives the error.
For instance, here's a reaction to a successfuly resolved promise:
```js run
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
// resolve runs the first function in .then
promise.then(
*!*
result => alert(result), // shows "done!" after 1 second
*/!*
error => alert(error) // doesn't run
);
```
The first function was executed.
And in the case of a rejection -- the second one:
```js run
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// reject runs the second function in .then
promise.then(
result => alert(result), // doesn't run
*!*
error => alert(error) // shows "Error: Whoops!" after 1 second
*/!*
);
```
If we're interested only in successful completions, then we can provide only one function argument to `.then`:
```js run
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000);
});
*!*
promise.then(alert); // shows "done!" after 1 second
*/!*
```
### catch
If we're interested only in errors, then we can use `null` as the first argument: `.then(null, errorHandlingFunction)`. Or we can use `.catch(errorHandlingFunction)`, which is exactly the same:
```js run
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
*!*
// .catch(f) is the same as promise.then(null, f)
promise.catch(alert); // shows "Error: Whoops!" after 1 second
*/!*
```
The call `.catch(f)` is a complete analog of `.then(null, f)`, it's just a shorthand.
### finally
The call `.finally(f)` is similar to `.then(f, f)`, it always runs when the promise is settled: be it resolve or reject.
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:
```js
new Promise((resolve, reject) => {
/* do something that takes time, and then call resolve/reject */
})
*!*
// runs when the promise is settled, doesn't matter successfully or not
.finally(() => stop loading indicator)
*/!*
.then(result => show result, err => show error)
```
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.
2. Finally passes through results and errors to the next handler.
For instance, here the result is passed through `finally` to `then`:
```js run
new Promise((resolve, reject) => {
setTimeout(() => resolve("result"), 2000)
})
.finally(() => alert("Promise ready"))
.then(result => alert(result)); // result
```
And here there's an error in the promise, passed through `finally` to `catch`:
```js run
new Promise((resolve, reject) => {
throw new Error("error");
})
.finally(() => alert("Promise ready"))
.catch(err => alert(err)); // error
```
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.
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.
````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:
```js run
// an immediately resolved promise
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.
````
Next, let's see more practical examples of how promises can help us to write asynchronous code.
## Example: loadScript
We've got the `loadScript` function for loading a script from the previous chapter.
Here's the callback-based variant, just to remind us of it:
```js
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`Script load error ` + src));
document.head.append(script);
}
```
Let's rewrite it using Promises.
The new function `loadScript` will not require a callback. Instead, it will create and return a Promise object that resolves when the loading is complete. The outer code can add handlers (subscribing functions) to it using `.then`:
```js run
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error("Script load error: " + src));
document.head.append(script);
});
}
```
Usage:
```js run
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");
promise.then(
script => alert(`${script.src} is loaded!`),
error => alert(`Error: ${error.message}`)
);
promise.then(script => alert('One more handler to do something else!'));
```
We can immediately see a few benefits over the callback-based pattern:
| Promises | Callbacks |
|----------|-----------|
| Promises allow us to do things in the natural order. First, we run `loadScript(script)`, and `.then` we write what to do with the result. | We must have a `callback` function at our disposal when calling `loadScript(script, callback)`. In other words, we must know what to do with the result *before* `loadScript` is called. |
| We can call `.then` on a Promise as many times as we want. Each time, we're adding a new "fan", a new subscribing function, to the "subscription list". More about this in the next chapter: [](info:promise-chaining). | There can be only one callback. |
So Promises give us better code flow and flexibility. But there's more. We'll see that in the next chapters.

View file

@ -0,0 +1,13 @@
<script>
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error("Script load error: " + src));
document.head.append(script);
});
}
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View file

@ -0,0 +1,20 @@
The short answer is: **no, they are not the equal**:
The difference is that if an error happens in `f1`, then it is handled by `.catch` here:
```js run
promise
.then(f1)
.catch(f2);
```
...But not here:
```js run
promise
.then(f1, f2);
```
That's because an error is passed down the chain, and in the second code piece there's no chain below `f1`.
In other words, `.then` passes results/errors to the next `.then/catch`. So in the first example, there's a `catch` below, and in the second one -- there isn't, so the error is unhandled.

View file

@ -0,0 +1,13 @@
# Promise: then versus catch
Are these code fragments equal? In other words, do they behave the same way in any circumstances, for any handler functions?
```js
promise.then(f1).catch(f2);
```
Versus:
```js
promise.then(f1, f2);
```

View file

@ -0,0 +1,389 @@
# Promises chaining
Let's return to the problem mentioned in the chapter <info:callbacks>.
- We have a sequence of asynchronous tasks to be done one after another. For instance, loading scripts.
- How to code it well?
Promises provide a couple of recipes to do that.
In this chapter we cover promise chaining.
It looks like this:
```js run
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
```
The idea is that the result is passed through the chain of `.then` handlers.
Here the flow is:
1. The initial promise resolves in 1 second `(*)`,
2. Then the `.then` handler is called `(**)`.
3. The value that it returns is passed to the next `.then` handler `(***)`
4. ...and so on.
As the result is passed along the chain of handlers, we can see a sequence of `alert` calls: `1` -> `2` -> `4`.
![](promise-then-chain.png)
The whole thing works, because a call to `promise.then` returns a promise, so that we can call the next `.then` on it.
When a handler returns a value, it becomes the result of that promise, so the next `.then` is called with it.
To make these words more clear, here's the start of the chain:
```js run
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result);
return result * 2; // <-- (1)
}) // <-- (2)
// .then…
```
The value returned by `.then` is a promise, that's why we are able to add another `.then` at `(2)`. When the value is returned in `(1)`, that promise becomes resolved, so the next handler runs with the value.
Please note: technically we can also add many `.then` to a single promise. This is not chaining:
```js run
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
```
What we did here is just several handlers to one promise. They don't pass the result to each other, instead they process it idependantly.
Here's the picture (compare it with the chaining above):
![](promise-then-many.png)
All `.then` on the same promise get the same result -- the result of that promise. So in the code above all `alert` show the same: `1`.
In practice we rarely need multiple handlers for one promise. Chaining is used much more often.
## Returning promises
Normally, a value returned by a `.then` handler is immediately passed to the next handler. But there's an exception.
If the returned value is a promise, then the further execution is suspended until it settles. After that, the result of that promise is given to the next `.then` handler.
For instance:
```js run
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
*!*
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
*/!*
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
```
Here the first `.then` shows `1` returns `new Promise(…)` in the line `(*)`. After one second it resolves, and the result (the argument of `resolve`, here it's `result*2`) is passed on to handler of the second `.then` in the line `(**)`. It shows `2` and does the same thing.
So the output is again 1 -> 2 -> 4, but now with 1 second delay between `alert` calls.
Returning promises allows us to build chains of asynchronous actions.
## Example: loadScript
Let's use this feature with `loadScript` to load scripts one by one, in sequence:
```js run
loadScript("/article/promise-chaining/one.js")
.then(function(script) {
return loadScript("/article/promise-chaining/two.js");
})
.then(function(script) {
return loadScript("/article/promise-chaining/three.js");
})
.then(function(script) {
// use functions declared in scripts
// to show that they indeed loaded
one();
two();
three();
});
```
This code can be made bit shorter with arrow functions:
```js run
loadScript("/article/promise-chaining/one.js")
.then(script => loadScript("/article/promise-chaining/two.js"))
.then(script => loadScript("/article/promise-chaining/three.js"))
.then(script => {
// scripts are loaded, we can use functions declared there
one();
two();
three();
});
```
Here each `loadScript` call returns a promise, and the next `.then` runs when it resolves. Then it initiates the loading of the next script. So scripts are loaded one after another.
We can add more asynchronous actions to the chain. Please note that code is still "flat", it grows down, not to the right. There are no signs of "pyramid of doom".
Please note that technically we can add `.then` directly to each `loadScript`, like this:
```js run
loadScript("/article/promise-chaining/one.js").then(script1 => {
loadScript("/article/promise-chaining/two.js").then(script2 => {
loadScript("/article/promise-chaining/three.js").then(script3 => {
// this function has access to variables script1, script2 and script3
one();
two();
three();
});
});
});
```
This code does the same: loads 3 scripts in sequence. But it "grows to the right". So we have the same problem as with callbacks.
People who start to use promises sometimes don't know about chaining, so they write it this way. Generally, chaining is preferred.
Sometimes it's ok to write `.then` directly, because the nested function has access to the outer scope. In the example above the most nested callback has access to all variables `script1`, `script2`, `script3`. But that's an exception rather than a rule.
````smart header="Thenables"
To be precise, `.then` may return an arbitrary "thenable" object, and it will be treated the same way as a promise.
A "thenable" object is any object with a method `.then`.
The idea is that 3rd-party libraries may implement "promise-compatible" objects of their own. They can have extended set of methods, but also be compatible with native promises, because they implement `.then`.
Here's an example of a thenable object:
```js run
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// resolve with this.num*2 after the 1 second
setTimeout(() => resolve(this.num * 2), 1000); // (**)
}
}
new Promise(resolve => resolve(1))
.then(result => {
return new Thenable(result); // (*)
})
.then(alert); // shows 2 after 1000ms
```
JavaScript checks the object returned by `.then` handler in the line `(*)`: if it has a callable method named `then`, then it calls that method providing native functions `resolve`, `reject` as arguments (similar to executor) and waits until one of them is called. In the example above `resolve(2)` is called after 1 second `(**)`. Then the result is passed further down the chain.
This feature allows to integrate custom objects with promise chains without having to inherit from `Promise`.
````
## Bigger example: fetch
In frontend programming promises are often used for network requests. So let's see an extended example of that.
We'll use the [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) method to load the information about the user from the remote server. The method is quite complex, it has many optional parameters, but the basic usage is quite simple:
```js
let promise = fetch(url);
```
This makes a network request to the `url` and returns a promise. The promise resolves with a `response` object when the remote server responds with headers, but *before the full response is downloaded*.
To read the full response, we should call a method `response.text()`: it returns a promise that resolves when the full text downloaded from the remote server, with that text as a result.
The code below makes a request to `user.json` and loads its text from the server:
```js run
fetch('/article/promise-chaining/user.json')
// .then below runs when the remote server responds
.then(function(response) {
// response.text() returns a new promise that resolves with the full response text
// when we finish downloading it
return response.text();
})
.then(function(text) {
// ...and here's the content of the remote file
alert(text); // {"name": "iliakan", isAdmin: true}
});
```
There is also a method `response.json()` that reads the remote data and parses it as JSON. In our case that's even more convenient, so let's switch to it.
We'll also use arrow functions for brevity:
```js run
// same as above, but response.json() parses the remote content as JSON
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => alert(user.name)); // iliakan
```
Now let's do something with the loaded user.
For instance, we can make one more request to github, load the user profile and show the avatar:
```js run
// Make a request for user.json
fetch('/article/promise-chaining/user.json')
// Load it as json
.then(response => response.json())
// Make a request to github
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// Load the response as json
.then(response => response.json())
// Show the avatar image (githubUser.avatar_url) for 3 seconds (maybe animate it)
.then(githubUser => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => img.remove(), 3000); // (*)
});
```
The code works, see comments about the details, but it should be quite self-descriptive. Although, there's a potential problem in it, a typical error of those who begin to use promises.
Look at the line `(*)`: how can we do something *after* the avatar has finished showing and gets removed? For instance, we'd like to show a form for editing that user or something else. As of now, there's no way.
To make the chain extendable, we need to return a promise that resolves when the avatar finishes showing.
Like this:
```js run
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
*!*
.then(githubUser => new Promise(function(resolve, reject) {
*/!*
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
*!*
resolve(githubUser);
*/!*
}, 3000);
}))
// triggers after 3 seconds
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
```
Now right after `setTimeout` runs `img.remove()`, it calls `resolve(githubUser)`, thus passing the control to the next `.then` in the chain and passing forward the user data.
As a rule, an asynchronous action should always return a promise.
That makes possible to plan actions after it. Even if we don't plan to extend the chain now, we may need it later.
Finally, we can split the code into reusable functions:
```js run
function loadJson(url) {
return fetch(url)
.then(response => response.json());
}
function loadGithubUser(name) {
return fetch(`https://api.github.com/users/${name}`)
.then(response => response.json());
}
function showAvatar(githubUser) {
return new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
});
}
// Use them:
loadJson('/article/promise-chaining/user.json')
.then(user => loadGithubUser(user.name))
.then(showAvatar)
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
// ...
```
## Summary
If `.then` (or `catch/finally`, doesn't matter) handler returns a promise, the rest of the chain waits until it settles. When it does, its result (or error) is passed further.
Here's a full picture:
![](promise-handler-variants.png)

View file

@ -0,0 +1,3 @@
function getMessage() {
return "Hello, world!";
}

View file

@ -0,0 +1,41 @@
<script>
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error("Script load error: " + src));
document.head.append(script);
});
}
class HttpError extends Error {
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
})
}
</script>
<style>
.promise-avatar-example {
border-radius: 50%;
position: fixed;
left: 10px;
top: 10px;
}
</style>

View file

@ -0,0 +1,3 @@
function one() {
alert(1);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -0,0 +1,3 @@
function three() {
alert(3);
}

View file

@ -0,0 +1,3 @@
function two() {
alert(2);
}

View file

@ -0,0 +1,4 @@
{
"name": "iliakan",
"isAdmin": true
}

View file

@ -0,0 +1,13 @@
The answer is: **no, it won't**:
```js run
new Promise(function(resolve, reject) {
setTimeout(() => {
throw new Error("Whoops!");
}, 1000);
}).catch(alert);
```
As said in the chapter, there's an "implicit `try..catch`" around the function code. So all synchronous errors are handled.
But here the error is generated not while the executor is running, but later. So the promise can't handle it.

View file

@ -0,0 +1,11 @@
# Error in setTimeout
What do you think? Will the `.catch` trigger? Explain your answer.
```js
new Promise(function(resolve, reject) {
setTimeout(() => {
throw new Error("Whoops!");
}, 1000);
}).catch(alert);
```

View file

@ -0,0 +1,343 @@
# Error handling with promises
Asynchronous actions may sometimes fail: in case of an error the corresponding promise becomes rejected. For instance, `fetch` fails if the remote server is not available. We can use `.catch` to handle errors (rejections).
Promise chaining is great at that aspect. When a promise rejects, the control jumps to the closest rejection handler down the chain. That's very convenient in practice.
For instance, in the code below the URL is wrong (no such server) and `.catch` handles the error:
```js run
*!*
fetch('https://no-such-server.blabla') // rejects
*/!*
.then(response => response.json())
.catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)
```
Or, maybe, everything is all right with the server, but the response is not a valid JSON:
```js run
fetch('/') // fetch works fine now, the server responds successfully
*!*
.then(response => response.json()) // rejects: the page is HTML, not a valid json
*/!*
.catch(err => alert(err)) // SyntaxError: Unexpected token < in JSON at position 0
```
The easiest way to catch all errors is to append `.catch` to the end of chain:
```js run
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise((resolve, reject) => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
*!*
.catch(error => alert(error.message));
*/!*
```
Normally, `.catch` doesn't trigger at all, because there are no errors. But if any of the promises above rejects (a network problem or invalid json or whatever), then it would catch it.
## Implicit try..catch
The code of a promise executor and promise handlers has an "invisible `try..catch`" around it. If an error happens, it gets caught and treated as a rejection.
For instance, this code:
```js run
new Promise((resolve, reject) => {
*!*
throw new Error("Whoops!");
*/!*
}).catch(alert); // Error: Whoops!
```
...Works exactly the same as this:
```js run
new Promise((resolve, reject) => {
*!*
reject(new Error("Whoops!"));
*/!*
}).catch(alert); // Error: Whoops!
```
The "invisible `try..catch`" around the executor automatically catches the error and treats it as a rejection.
That's so not only in the executor, but in handlers as well. If we `throw` inside `.then` handler, that means a rejected promise, so the control jumps to the nearest error handler.
Here's an example:
```js run
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
*!*
throw new Error("Whoops!"); // rejects the promise
*/!*
}).catch(alert); // Error: Whoops!
```
That's so not only for `throw`, but for any errors, including programming errors as well:
```js run
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
*!*
blabla(); // no such function
*/!*
}).catch(alert); // ReferenceError: blabla is not defined
```
As a side effect, the final `.catch` not only catches explicit rejections, but also occasional errors in the handlers above.
## Rethrowing
As we already noticed, `.catch` behaves like `try..catch`. We may have as many `.then` as we want, and then use a single `.catch` at the end to handle errors in all of them.
In a regular `try..catch` we can analyze the error and maybe rethrow it if can't handle. The same thing is possible for promises.
If we `throw` inside `.catch`, then the control goes to the next closest error handler. And if we handle the error and finish normally, then it continues to the closest successful `.then` handler.
In the example below the `.catch` successfully handles the error:
```js run
// the execution: catch -> then
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) {
alert("The error is handled, continue normally");
}).then(() => alert("Next successful handler runs"));
```
Here the `.catch` block finishes normally. So the next successful `.then` handler is called.
In the example below we see the other situation with `.catch`. The handler `(*)` catches the error and just can't handle it (e.g. it only knows how to handle `URIError`), so it throws it again:
```js run
// the execution: catch -> catch -> then
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) { // (*)
if (error instanceof URIError) {
// handle it
} else {
alert("Can't handle such error");
*!*
throw error; // throwing this or another error jumps to the next catch
*/!*
}
}).then(function() {
/* never runs here */
}).catch(error => { // (**)
alert(`The unknown error has occurred: ${error}`);
// don't return anything => execution goes the normal way
});
```
Then the execution jumps from the first `.catch` `(*)` to the next one `(**)` down the chain.
In the section below we'll see a practical example of rethrowing.
## Fetch error handling example
Let's improve error handling for the user-loading example.
The promise returned by [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) rejects when it's impossible to make a request. For instance, a remote server is not available, or the URL is malformed. But if the remote server responds with error 404, or even error 500, then it's considered a valid response.
What if the server returns a non-JSON page with error 500 in the line `(*)`? What if there's no such user, and github returns a page with error 404 at `(**)`?
```js run
fetch('no-such-user.json') // (*)
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`)) // (**)
.then(response => response.json())
.catch(alert); // SyntaxError: Unexpected token < in JSON at position 0
// ...
```
As of now, the code tries to load the response as JSON no matter what and dies with a syntax error. You can see that by running the example above, as the file `no-such-user.json` doesn't exist.
That's not good, because the error just falls through the chain, without details: what failed and where.
So let's add one more step: we should check the `response.status` property that has HTTP status, and if it's not 200, then throw an error.
```js run
class HttpError extends Error { // (1)
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) { // (2)
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
})
}
loadJson('no-such-user.json') // (3)
.catch(alert); // HttpError: 404 for .../no-such-user.json
```
1. We make a custom class for HTTP Errors to distinguish them from other types of errors. Besides, the new class has a constructor that accepts `response` object and saves it in the error. So error-handling code will be able to access it.
2. Then we put together the requesting and error-handling code into a function that fetches the `url` *and* treats any non-200 status as an error. That's convenient, because we often need such logic.
3. Now `alert` shows better message.
The great thing about having our own class for errors is that we can easily check for it in error-handling code.
For instance, we can make a request, and then if we get 404 -- ask the user to modify the information.
The code below loads a user with the given name from github. If there's no such user, then it asks for the correct name:
```js run
function demoGithubUser() {
let name = prompt("Enter a name?", "iliakan");
return loadJson(`https://api.github.com/users/${name}`)
.then(user => {
alert(`Full name: ${user.name}.`);
return user;
})
.catch(err => {
*!*
if (err instanceof HttpError && err.response.status == 404) {
*/!*
alert("No such user, please reenter.");
return demoGithubUser();
} else {
throw err; // (*)
}
});
}
demoGithubUser();
```
Please note: `.catch` here catches all errors, but it "knows how to handle" only `HttpError 404`. In that particular case it means that there's no such user, and `.catch` just retries in that case.
For other errors, it has no idea what could go wrong. Maybe a programming error or something. So it just rethrows it in the line `(*)`.
## Unhandled rejections
What happens when an error is not handled? For instance, after the rethrow `(*)` in the example above.
Or we could just forget to append an error handler to the end of the chain, like here:
```js untrusted run refresh
new Promise(function() {
noSuchFunction(); // Error here (no such function)
})
.then(() => {
// zero or many promise handlers
}); // without .catch at the end!
```
In case of an error, the promise state becomes "rejected", and the execution should jump to the closest rejection handler. But there is no such handler in the examples above. So the error gets "stuck".
In practice, just like with a regular unhandled errors, it means that something terribly gone wrong, the script probably died.
Most JavaScript engines track such situations and generate a global error in that case. We can see it in the console.
In the browser we can catch such errors using the event `unhandledrejection`:
```js run
*!*
window.addEventListener('unhandledrejection', function(event) {
// the event object has two special properties:
alert(event.promise); // [object Promise] - the promise that generated the error
alert(event.reason); // Error: Whoops! - the unhandled error object
});
*/!*
new Promise(function() {
throw new Error("Whoops!");
}); // no catch to handle the error
```
The event is the part of the [HTML standard](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections).
If an error occurs, and there's no `.catch`, the `unhandledrejection` handler triggers, and gets the `event` object with the information about the error, so we can do something.
Usually such errors are unrecoverable, so our best way out is to inform the user about the problem and probably report the incident to the server.
In non-browser environments like Node.JS there are other similar ways to track unhandled errors.
## Summary
- `.catch` handles promise rejections of all kinds: be it a `reject()` call, or an error thrown in a handler.
- We should place `.catch` exactly in places where we want to handle errors and know how to handle them. The handler should analyze errors (custom error classes help) and rethrow unknown ones.
- It's normal not to use `.catch` if we don't know how to handle errors (all errors are unrecoverable).
- In any case we should have the `unhandledrejection` event handler (for browsers, and analogs for other environments), to track unhandled errors and inform the user (and probably our server) about the them. So that our app never "just dies".
And finally, if we have load-indication, then `.finally` is a great handler to stop it when the fetch is complete:
```js run
function demoGithubUser() {
let name = prompt("Enter a name?", "iliakan");
*!*
document.body.style.opacity = 0.3; // (1) start the indication
*/!*
return loadJson(`https://api.github.com/users/${name}`)
*!*
.finally(() => { // (2) stop the indication
document.body.style.opacity = '';
return new Promise(resolve => setTimeout(resolve, 0)); // (*)
})
*/!*
.then(user => {
alert(`Full name: ${user.name}.`);
return user;
})
.catch(err => {
if (err instanceof HttpError && err.response.status == 404) {
alert("No such user, please reenter.");
return demoGithubUser();
} else {
throw err;
}
});
}
demoGithubUser();
```
Here on the line `(1)` we indicate loading by dimming the document. The method doesn't matter, could use any type of indication instead.
When the promise is settled, be it a successful fetch or an error, `finally` triggers at the line `(2)` and stops the indication.
There's a little browser trick `(*)` with returning a zero-timeout promise from `finally`. That's because some browsers (like Chrome) need "a bit time" outside promise handlers to paint document changes. So it ensures that the indication is visually stopped before going further on the chain.

View file

@ -0,0 +1,3 @@
function getMessage() {
return "Hello, world!";
}

View file

@ -0,0 +1,41 @@
<script>
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error("Script load error: " + src));
document.head.append(script);
});
}
class HttpError extends Error {
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
})
}
</script>
<style>
.promise-avatar-example {
border-radius: 50%;
position: fixed;
left: 10px;
top: 10px;
}
</style>

View file

@ -0,0 +1,3 @@
function one() {
alert(1);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -0,0 +1,3 @@
function three() {
alert(3);
}

View file

@ -0,0 +1,3 @@
function two() {
alert(2);
}

View file

@ -0,0 +1,4 @@
{
"name": "iliakan",
"isAdmin": true
}

View file

@ -0,0 +1,44 @@
The solution is actually pretty simple.
Take a look at this:
```js
Promise.all(
fetch('https://api.github.com/users/iliakan'),
fetch('https://api.github.com/users/remy'),
fetch('http://no-such-url')
)
```
Here we have an array of `fetch(...)` promises that goes to `Promise.all`.
We can't change the way `Promise.all` works: if it detects an error, then it rejects with it. So we need to prevent any error from occurring. Instead, if a `fetch` error happens, we need to treat it as a "normal" result.
Here's how:
```js
Promise.all(
fetch('https://api.github.com/users/iliakan').catch(err => err),
fetch('https://api.github.com/users/remy').catch(err => err),
fetch('http://no-such-url').catch(err => err)
)
```
In other words, the `.catch` takes an error for all of the promises and returns it normally. By the rules of how promises work, if a `.then/catch` handler returns a value (doesn't matter if it's an error object or something else), then the execution continues the "normal" flow.
So the `.catch` returns the error as a "normal" result into the outer `Promise.all`.
This code:
```js
Promise.all(
urls.map(url => fetch(url))
)
```
Can be rewritten as:
```js
Promise.all(
urls.map(url => fetch(url).catch(err => err))
)
```

View file

@ -0,0 +1,24 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'http://no-such-url'
];
Promise.all(
urls.map(url => fetch(url).catch(err => err))
)
.then(responses => {
alert(responses[0].status); // 200
alert(responses[1].status); // 200
alert(responses[2]); // TypeError: failed to fetch (text may vary)
});
</script>
</body>
</html>

View file

@ -0,0 +1,28 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
// if any of URLs fails, other results are ignored
// change that:
// make errors appear as members of the responses array, together with normal results
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'http://no-such-url'
];
// Fix me:
Promise.all(urls.map(url => fetch(url)))
// Demo output (no need to change):
.then(responses => {
alert(responses[0].status); // 200
alert(responses[1].status); // 200
alert(responses[2]); // TypeError: failed to fetch (text may vary)
});
</script>
</body>
</html>

View file

@ -0,0 +1,48 @@
# Fault-tolerant Promise.all
We'd like to fetch multiple URLs in parallel.
Here's the code to do that:
```js run
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
Promise.all(urls.map(url => fetch(url)))
// for each response show its status
.then(responses => { // (*)
for(let response of responses) {
alert(`${response.url}: ${response.status}`);
}
});
```
The problem is that if any of requests fails, then `Promise.all` rejects with the error, and we lose results of all the other requests.
That's not good.
Modify the code so that the array `responses` in the line `(*)` would include the response objects for successful fetches and error objects for failed ones.
For instance, if one of URLs is bad, then it should be like:
```js
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'http://no-such-url'
];
Promise.all(...) // your code to fetch URLs...
// ...and pass fetch errors as members of the resulting array...
.then(responses => {
// 3 urls => 3 array members
alert(responses[0].status); // 200
alert(responses[1].status); // 200
alert(responses[2]); // TypeError: failed to fetch (text may vary)
});
```
P.S. In this task you don't have to load the full response using `response.text()` or `response.json()`. Just handle fetch errors the right way.

View file

@ -0,0 +1,29 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
let urls = [
'https://api.github.com/users/iliakan',
'/',
'http://no-such-url'
];
Promise.all(
urls.map(url => fetch(url).catch(err => err))
)
.then(responses => Promise.all(
// if it's an error then pass on
// otherwise response.json() and catch errors as results
responses.map(r => r instanceof Error ? r : r.json().catch(err => err))
))
.then(results => {
alert(results[0].name); // Ilya Kantor
alert(results[1]); // SyntaxError: Unexpected token < in JSON at position 0
alert(results[2]); // TypeError: failed to fetch (text may vary)
});
</script>
</body>
</html>

View file

@ -0,0 +1,33 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
// the whole promise chain fails with an error here
// change that:
// make errors appear as members of the results array
let urls = [
'https://api.github.com/users/iliakan',
// this URL is HTML page, it's invalid JSON, so response.json() fails
'/',
// this URL is invalid, so fetch fails
'http://no-such-url'
];
// Fix it:
Promise.all(urls.map(url => fetch(url)))
.then(responses => Promise.all(
responses.map(r => r.json())
))
// Demo output (no need to change):
.then(results => {
alert(results[0].name); // Ilya Kantor
alert(results[1]); // SyntaxError: Unexpected token < in JSON at position 0
alert(results[2]); // TypeError: failed to fetch (text may vary)
});
</script>
</body>
</html>

View file

@ -0,0 +1,34 @@
# Fault-tolerant fetch with JSON
Improve the solution of the previous task <info:task/promise-errors-as-results>. Now we need not just to call `fetch`, but to load the JSON objects from given URLs.
Here's the example code to do that:
```js run
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
// make fetch requests
Promise.all(urls.map(url => fetch(url)))
// map each response to response.json()
.then(responses => Promise.all(
responses.map(r => r.json())
))
// show name of each user
.then(users => { // (*)
for(let user of users) {
alert(user.name);
}
});
```
The problem is that if any of requests fails, then `Promise.all` rejects with the error, and we lose results of all the other requests. So the code above is not fault-tolerant, just like the one in the previous task.
Modify the code so that the array in the line `(*)` would include parsed JSON for successful requests and error for errored ones.
Please note that the error may occur both in `fetch` (if the network request fails) and in `response.json()` (if the response is invalid JSON). In both cases the error should become a member of the results object.
The sandbox has both of these cases.

View file

@ -0,0 +1,202 @@
# Promise API
There are 4 static methods in the `Promise` class. We'll quickly cover their use cases here.
## Promise.resolve
The syntax:
```js
let promise = Promise.resolve(value);
```
Returns a resolved promise with the given `value`.
Same as:
```js
let promise = new Promise(resolve => resolve(value));
```
The method is used when we already have a value, but would like to have it "wrapped" into a promise.
For instance, the `loadCached` function below fetches the `url` and remembers the result, so that future calls on the same URL return it immediately:
```js
function loadCached(url) {
let cache = loadCached.cache || (loadCached.cache = new Map());
if (cache.has(url)) {
*!*
return Promise.resolve(cache.get(url)); // (*)
*/!*
}
return fetch(url)
.then(response => response.text())
.then(text => {
cache.set(url,text);
return text;
});
}
```
We can use `loadCached(url).then(…)`, because the function is guaranteed to return a promise. That's the purpose `Promise.resolve` in the line `(*)`: it makes sure the interface unified. We can always use `.then` after `loadCached`.
## Promise.reject
The syntax:
```js
let promise = Promise.reject(error);
```
Create a rejected promise with the `error`.
Same as:
```js
let promise = new Promise((resolve, reject) => reject(error));
```
We cover it here for completeness, rarely used in real code.
## Promise.all
The method to run many promises in parallel and wait till all of them are ready.
The syntax is:
```js
let promise = Promise.all(iterable);
```
It takes an `iterable` object with promises, technically it can be any iterable, but usually it's an array, and returns a new promise. The new promise resolves with when all of them are settled and has an array of their results.
For instance, the `Promise.all` below settles after 3 seconds, and then its result is an array `[1, 2, 3]`:
```js run
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 3000)), // 1
new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000)), // 2
new Promise((resolve, reject) => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); // 1,2,3 when promises are ready: each promise contributes an array member
```
Please note that the relative order is the same. Even though the first promise takes the longest time to resolve, it is still first in the array of results.
A common trick is to map an array of job data into an array of promises, and then wrap that into `Promise.all`.
For instance, if we have an array of URLs, we can fetch them all like this:
```js run
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
// map every url to the promise fetch(github url)
let requests = urls.map(url => fetch(url));
// Promise.all waits until all jobs are resolved
Promise.all(requests)
.then(responses => responses.forEach(
response => alert(`${response.url}: ${response.status}`)
));
```
A more real-life example with fetching user information for an array of github users by their names (or we could fetch an array of goods by their ids, the logic is same):
```js run
let names = ['iliakan', 'remy', 'jeresig'];
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
.then(responses => {
// all responses are ready, we can show HTTP status codes
for(let response of responses) {
alert(`${response.url}: ${response.status}`); // shows 200 for every url
}
return responses;
})
// map array of responses into array of response.json() to read their content
.then(responses => Promise.all(responses.map(r => r.json())))
// all JSON answers are parsed: "users" is the array of them
.then(users => users.forEach(user => alert(user.name)));
```
If any of the promises is rejected, `Promise.all` immediately rejects with that error.
For instance:
```js run
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
*!*
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
*/!*
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: Whoops!
```
Here the second promise rejects in two seconds. That leads to immediate rejection of `Promise.all`, so `.catch` executes: the rejection error becomes the outcome of the whole `Promise.all`.
The important detail is that promises provide no way to "cancel" or "abort" their execution. So other promises continue to execute, and the eventually settle, but all their results are ignored.
There are ways to avoid this: we can either write additional code to `clearTimeout` (or otherwise cancel) the promises in case of an error, or we can make errors show up as members in the resulting array (see the task below this chapter about it).
````smart header="`Promise.all(iterable)` allows non-promise items in `iterable`"
Normally, `Promise.all(iterable)` accepts an iterable (in most cases an array) of promises. But if any of those objects is not a promise, it's wrapped in `Promise.resolve`.
For instance, here the results are `[1, 2, 3]`:
```js run
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000)
}),
2, // treated as Promise.resolve(2)
3 // treated as Promise.resolve(3)
]).then(alert); // 1, 2, 3
```
So we are able to pass non-promise values to `Promise.all` where convenient.
````
## Promise.race
Similar to `Promise.all` takes an iterable of promises, but instead of waiting for all of them to finish -- waits for the first result (or error), and goes on with it.
The syntax is:
```js
let promise = Promise.race(iterable);
```
For instance, here the result will be `1`:
```js run
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
```
So, the first result/error becomes the result of the whole `Promise.race`. After the first settled promise "wins the race", all further results/errors are ignored.
## Summary
There are 4 static methods of `Promise` class:
1. `Promise.resolve(value)` -- makes a resolved promise with the given value,
2. `Promise.reject(error)` -- makes a rejected promise with the given error,
3. `Promise.all(promises)` -- waits for all promises to resolve and returns an array of their results. If any of the given promises rejects, then it becomes the error of `Promise.all`, and all other results are ignored.
4. `Promise.race(promises)` -- waits for the first promise to settle, and its result/error becomes the outcome.
Of these four, `Promise.all` is the most common in practice.

View file

@ -0,0 +1,13 @@
<script>
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error("Script load error: " + src));
document.head.append(script);
});
}
</script>

View file

@ -0,0 +1,4 @@
{
"name": "iliakan",
"isAdmin": true
}

View file

@ -0,0 +1,3 @@
function one() {
alert(1);
}

View file

@ -0,0 +1,3 @@
function two() {
alert(2);
}

View file

@ -0,0 +1,49 @@
There are no tricks here. Just replace `.catch` with `try...catch` inside `demoGithubUser` and add `async/await` where needed:
```js run
class HttpError extends Error {
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
async function loadJson(url) {
let response = await fetch(url);
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
}
// Ask for a user name until github returns a valid user
async function demoGithubUser() {
let user;
while(true) {
let name = prompt("Enter a name?", "iliakan");
try {
user = await loadJson(`https://api.github.com/users/${name}`);
break; // no error, exit loop
} catch(err) {
if (err instanceof HttpError && err.response.status == 404) {
// loop continues after the alert
alert("No such user, please reenter.");
} else {
// unknown error, rethrow
throw err;
}
}
}
alert(`Full name: ${user.name}.`);
return user;
}
demoGithubUser();
```

View file

@ -0,0 +1,48 @@
# Rewrite "rethrow" async/await
Below you can find the "rethrow" example from the chapter <info:promise-chaining>. Rewrite it using `async/await` instead of `.then/catch`.
And get rid of the recursion in favour of a loop in `demoGithubUser`: with `async/await` that becomes easy to do.
```js run
class HttpError extends Error {
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
})
}
// Ask for a user name until github returns a valid user
function demoGithubUser() {
let name = prompt("Enter a name?", "iliakan");
return loadJson(`https://api.github.com/users/${name}`)
.then(user => {
alert(`Full name: ${user.name}.`);
return user;
})
.catch(err => {
if (err instanceof HttpError && err.response.status == 404) {
alert("No such user, please reenter.");
return demoGithubUser();
} else {
throw err;
}
});
}
demoGithubUser();
```

View file

@ -0,0 +1,33 @@
The notes are below the code:
```js run
async function loadJson(url) { // (1)
let response = await fetch(url); // (2)
if (response.status == 200) {
let json = await response.json(); // (3)
return json;
}
throw new Error(response.status);
}
loadJson('no-such-user.json')
.catch(alert); // Error: 404 (4)
```
Notes:
1. The function `loadUrl` becomes `async`.
2. All `.then` inside are replaced with `await`.
3. We can `return response.json()` instead of awaiting for it, like this:
```js
if (response.status == 200) {
return response.json(); // (3)
}
```
Then the outer code would have to `await` for that promise to resolve. In our case it doesn't matter.
4. The error thrown from `loadJson` is handled by `.catch`. We can't use `await loadJson(…)` there, because we're not in an `async` function.

View file

@ -0,0 +1,20 @@
# Rewrite using async/await
Rewrite the one of examples from the chapter <info:promise-chaining> using `async/await` instead of `.then/catch`:
```js run
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new Error(response.status);
}
})
}
loadJson('no-such-user.json') // (3)
.catch(alert); // Error: 404
```

View file

@ -0,0 +1,298 @@
# Async/await
There's a special syntax to work with promises in a more comfortable fashion, called "async/await". It's surprisingly easy to understand and use.
## Async functions
Let's start with the `async` keyword. It can be placed before a function, like this:
```js
async function f() {
return 1;
}
```
The word "async" before a function means one simple thing: a function always returns a promise. Even If a function actually returns a non-promise value, prepending the function definition with the "async" keyword directs Javascript to automatically wrap that value in a resolved promise.
For instance, the code above returns a resolved promise with the result of `1`, let's test it:
```js run
async function f() {
return 1;
}
f().then(alert); // 1
```
...We could explicitly return a promise, that would be the same:
```js run
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
```
So, `async` ensures that the function returns a promise, and wraps non-promises in it. Simple enough, right? But not only that. There's another keyword, `await`, that works only inside `async` functions, and it's pretty cool.
## Await
The syntax:
```js
// works only inside async functions
let value = await promise;
```
The keyword `await` makes JavaScript wait until that promise settles and returns its result.
Here's an example with a promise that resolves in 1 second:
```js run
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
*!*
let result = await promise; // wait till the promise resolves (*)
*/!*
alert(result); // "done!"
}
f();
```
The function execution "pauses" at the line `(*)` and resumes when the promise settles, with `result` becoming its result. So the code above shows "done!" in one second.
Let's emphasize: `await` literally makes JavaScript wait until the promise settles, and then go on with the result. That doesn't cost any CPU resources, because the engine can do other jobs meanwhile: execute other scripts, handle events etc.
It's just a more elegant syntax of getting the promise result than `promise.then`, easier to read and write.
````warn header="Can't use `await` in regular functions"
If we try to use `await` in non-async function, that would be a syntax error:
```js run
function f() {
let promise = Promise.resolve(1);
*!*
let result = await promise; // Syntax error
*/!*
}
```
We will get this error if we do not put `async` before a function. As said, `await` only works inside an `async function`.
````
Let's take the `showAvatar()` example from the chapter <info:promise-chaining> and rewrite it using `async/await`:
1. We'll need to replace `.then` calls with `await`.
2. Also we should make the function `async` for them to work.
```js run
async function showAvatar() {
// read our JSON
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// read github user
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// show the avatar
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// wait 3 seconds
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
showAvatar();
```
Pretty clean and easy to read, right? Much better than before.
````smart header="`await` won't work in the top-level code"
People who are just starting to use `await` tend to forget the fact that we can't use `await` in top-level code. For example, this will not work:
```js run
// syntax error in top-level code
let response = await fetch('/article/promise-chaining/user.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.
````
````smart header="`await` accepts thenables"
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`.
For instance, here `await` accepts `new Thenable(1)`:
```js run
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// resolve with this.num*2 after 1000ms
setTimeout(() => resolve(this.num * 2), 1000); // (*)
}
};
async function f() {
// waits for 1 second, then result becomes 2
let result = await new Thenable(1);
alert(result);
}
f();
```
If `await` gets a non-promise object with `.then`, it calls that method providing native functions `resolve`, `reject` as arguments. Then `await` waits until one of them is called (in the example above it happens in the line `(*)`) and then proceeds with the result.
````
````smart header="Async methods"
A class method can also be async, just put `async` before it.
Like here:
```js run
class Waiter {
*!*
async wait() {
*/!*
return await Promise.resolve(1);
}
}
new Waiter()
.wait()
.then(alert); // 1
```
The meaning is the same: it ensures that the returned value is a promise and enables `await`.
````
## Error handling
If a promise resolves normally, then `await promise` returns the result. But in case of a rejection, it throws the error, just as if there were a `throw` statement at that line.
This code:
```js
async function f() {
*!*
await Promise.reject(new Error("Whoops!"));
*/!*
}
```
...Is the same as this:
```js
async function f() {
*!*
throw new Error("Whoops!");
*/!*
}
```
In real situations, the promise may take some time before it rejects. So `await` will wait, and then throw an error.
We can catch that error using `try..catch`, the same way as a regular `throw`:
```js run
async function f() {
try {
let response = await fetch('http://no-such-url');
} catch(err) {
*!*
alert(err); // TypeError: failed to fetch
*/!*
}
}
f();
```
In case of an error, the control jumps to the `catch` block. We can also wrap multiple lines:
```js run
async function f() {
try {
let response = await fetch('/no-user-here');
let user = await response.json();
} catch(err) {
// catches errors both in fetch and response.json
alert(err);
}
}
f();
```
If we don't have `try..catch`, then the promise generated by the call of the async function `f()` becomes rejected. We can append `.catch` to handle it:
```js run
async function f() {
let response = await fetch('http://no-such-url');
}
// f() becomes a rejected promise
*!*
f().catch(alert); // TypeError: failed to fetch // (*)
*/!*
```
If we forget to add `.catch` there, then we get an unhandled promise error (and can see it in the console). We can catch such errors using a global event handler as described in the chapter <info:promise-chaining>.
```smart header="`async/await` and `promise.then/catch`"
When we use `async/await`, we rarely need `.then`, because `await` handles the waiting for us. And we can use a regular `try..catch` instead of `.catch`. That's usually (not always) more convenient.
But at the top level of the code, when we're outside of any `async` function, we're syntactically unable to use `await`, so it's a normal practice to add `.then/catch` to handle the final result or falling-through errors.
Like in the line `(*)` of the example above.
```
````smart header="`async/await` works well with `Promise.all`"
When we need to wait for multiple promises, we can wrap them in `Promise.all` and then `await`:
```js
// wait for the array of results
let results = await Promise.all([
fetch(url1),
fetch(url2),
...
]);
```
In case of an error, it propagates as usual: from the failed promise to `Promise.all`, and then becomes an exception that we can catch using `try..catch` around the call.
````
## Summary
The `async` keyword before a function has two effects:
1. Makes it always return a promise.
2. Allows to use `await` in it.
The `await` keyword before a promise makes JavaScript wait until that promise settles, and then:
1. If it's an error, the exception is generated, same as if `throw error` were called at that very place.
2. Otherwise, it returns the result, so we can assign it to a value.
Together they provide a great framework to write asynchronous code that is easy both to read and write.
With `async/await` we rarely need to write `promise.then/catch`, but we still shouldn't forget that they are based on promises, because sometimes (e.g. in the outermost scope) we have to use these methods. Also `Promise.all` is a nice thing to wait for many tasks simultaneously.

View file

@ -0,0 +1,8 @@
<style>
.promise-avatar-example {
border-radius: 50%;
position: fixed;
left: 10px;
top: 10px;
}
</style>

View file

@ -0,0 +1,361 @@
# Async iteration and generators
Asynchronous iterators allow to iterate over data that comes asynchronously, on-demand.
For instance, when we download something chunk-by-chunk, or just expect events to come asynchronously and would like to iterate over them -- async iterators and generators may come in handy. Let's see a simple example first, to grasp the syntax, and then review a real-life use case.
## Async iterators
Asynchronous iterators are totally similar to regular iterators, with a few syntactic differences.
"Regular" iterable object from the chapter <info:iterable> look like this:
```js run
let range = {
from: 1,
to: 5,
// for..of calls this method once in the very beginning
*!*
[Symbol.iterator]() {
*/!*
// ...it returns the iterator object:
// onward, for..of works only with that object, asking it for next values
return {
current: this.from,
last: this.to,
// next() is called on each iteration by the for..of loop
*!*
next() { // (2)
// it should return the value as an object {done:.., value :...}
*/!*
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
for(let value in range) {
alert(value); // 1 then 2, then 3, then 4, then 5
}
```
If necessary, please refer to the [chapter about iterables](info:iterable) for details about regular iterators.
To make the object iterable asynchronously:
1. We need to use `Symbol.asyncIterator` instead of `Symbol.iterator`.
2. `next()` should return a promise.
3. To iterate over such an object, we should use `for await (let item of iterable)` loop.
Let's make an iterable `range` object, like the one before, but now it will return values asynchronously, one per second:
```js run
let range = {
from: 1,
to: 5,
// for await..of calls this method once in the very beginning
*!*
[Symbol.asyncIterator]() { // (1)
*/!*
// ...it returns the iterator object:
// onward, for await..of works only with that object, asking it for next values
return {
current: this.from,
last: this.to,
// next() is called on each iteration by the for..of loop
*!*
async next() { // (2)
// it should return the value as an object {done:.., value :...}
// (automatically wrapped into a promise by async)
*/!*
// can use await inside, do async stuff:
await new Promise(resolve => setTimeout(resolve, 1000)); // (3)
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
(async () => {
*!*
for await (let value of range) { // (4)
alert(value); // 1,2,3,4,5
}
*/!*
})()
```
As we can see, the components are similar to regular iterators:
1. To make an object asynchronously iterable, it must have a method `Symbol.asyncIterator` `(1)`.
2. It must return the object with `next()` method returning a promise `(2)`.
3. The `next()` method doesn't have to be `async`, it may be a regular method returning a promise, but `async` allows to use `await` inside. Here we just delay for a second `(3)`.
4. To iterate, we use `for await(let value of range)` `(4)`, namely add "await" after "for". It calls `range[Symbol.asyncIterator]()` once, and then its `next()` for values.
Here's a small cheatsheet:
| | Iterators | Async iterators |
|-------|-----------|-----------------|
| Object method to provide iteraterable | `Symbol.iterator` | `Symbol.asyncIterator` |
| `next()` return value is | any value | `Promise` |
| to loop, use | `for..of` | `for await..of` |
````warn header="The spread operator doesn't work asynchronously"
Features that require regular, synchronous iterators, don't work with asynchronous ones.
For instance, a spread operator won't work:
```js
alert( [...range] ); // Error, no Symbol.iterator
```
That's natural, as it expects to find `Symbol.iterator`, same as `for..of` without `await`.
````
## Async generators
Javascript also provides generators, that are also iterable.
Let's recall a sequence generator from the chapter [](info:generators). It generates a sequence of values from `start` to `end` (could be anything else):
```js run
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
for(let value of generateSequence(1, 5)) {
alert(value); // 1, then 2, then 3, then 4, then 5
}
```
Normally, we can't use `await` in generators. All values must come synchronously: there's no place for delay in `for..of`.
But what if we need to use `await` in the generator body? To perform network requests, for instance.
No problem, just prepend it with `async`, like this:
```js run
*!*async*/!* function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
*!*
// yay, can use await!
await new Promise(resolve => setTimeout(resolve, 1000));
*/!*
yield i;
}
}
(async () => {
let generator = generateSequence(1, 5);
for *!*await*/!* (let value of generator) {
alert(value); // 1, then 2, then 3, then 4, then 5
}
})();
```
Now we have an the async generator, iteratable with `for await...of`.
It's indeed very simple. We add the `async` keyword, and the generator now can use `await` inside of it, rely on promises and other async functions.
Technically, another the difference of an async generator is that its `generator.next()` method is now asynchronous also, it returns promises.
Instead of `result = generator.next()` for a regular, non-async generator, values can be obtained like this:
```js
result = await generator.next(); // result = {value: ..., done: true/false}
```
## Iterables via async generators
When we'd like to make an object iterable, we should add `Symbol.iterator` to it.
```js
let range = {
from: 1,
to: 5,
*!*
[Symbol.iterator]() { ...return object with next to make range iterable... }
*/!*
}
```
A common practice for `Symbol.iterator` is to return a generator, rather than a plain object with `next` as in the example before.
Let's recall an example from the chapter [](info:generators):
```js run
let range = {
from: 1,
to: 5,
*[Symbol.iterator]() { // a shorthand for [Symbol.iterator]: function*()
for(let value = this.from; value <= this.to; value++) {
yield value;
}
}
};
for(let value of range) {
alert(value); // 1, then 2, then 3, then 4, then 5
}
```
Here a custom object `range` is iterable, and the generator `*[Symbol.iterator]` implements the logic for listing values.
If we'd like to add async actions into the generator, then we should replace `Symbol.iterator` with async `Symbol.asyncIterator`:
```js run
let range = {
from: 1,
to: 5,
*!*
async *[Symbol.asyncIterator]() { // same as [Symbol.asyncIterator]: async function*()
*/!*
for(let value = this.from; value <= this.to; value++) {
// make a pause between values, wait for something
await new Promise(resolve => setTimeout(resolve, 1000));
yield value;
}
}
};
(async () => {
for *!*await*/!* (let value of range) {
alert(value); // 1, then 2, then 3, then 4, then 5
}
})();
```
Now values come with a delay of 1 second between them.
## Real-life example
So far we've seen simple examples, to gain basic understanding. Now let's review a real-life use case.
There are many online APIs that deliver paginated data. For instance, when we need a list of users, then we can fetch it page-by-page: a request returns a pre-defined count (e.g. 100 users), and provides an URL to the next page.
The pattern is very common, it's not about users, but just about anything. For instance, Github allows to retrieve commits in the same, paginated fasion:
- We should make a request to URL in the form `https://api.github.com/repos/<repo>/commits`.
- It responds with a JSON of 30 commits, and also provides a link to the next page in the `Link` header.
- Then we can use that link for the next request, to get more commits, and so on.
What we'd like to have is an iterable source of commits, so that we could use it like this:
```js
let repo = 'iliakan/javascript-tutorial-en'; // Github repository to get commits from
for await (let commit of fetchCommits(repo)) {
// process commit
}
```
We'd like `fetchCommits` to get commits for us, making requests whenever needed. And let it care about all pagination stuff, for us it'll be a simple `for await..of`.
With async generators that's pretty easy to implement:
```js
async function* fetchCommits(repo) {
let url = `https://api.github.com/repos/${repo}/commits`;
while (url) {
const response = await fetch(url, { // (1)
headers: {'User-Agent': 'Our script'}, // github requires user-agent header
});
const body = await response.json(); // (2) parses response as JSON (array of commits)
// (3) the URL of the next page is in the headers, extract it
let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/);
nextPage = nextPage && nextPage[1];
url = nextPage;
for(let commit of body) { // (4) yield commits one by one, until the page ends
yield commit;
}
}
}
```
1. We use the browser `fetch` method to download from a remote URL. It allows to supply authorization and other headers if needed, here Github requires `User-Agent`.
2. The fetch result is parsed as JSON, that's again a `fetch`-specific method.
3. We can get the next page URL from the `Link` header of the response. It has a special format, so we use a regexp for that. The next page URL may look like this: `https://api.github.com/repositories/93253246/commits?page=2`, it's generatd by Github itself.
4. Then we yield all commits received, and when they finish -- the next `while(url)` iteration will trigger, making one more request.
An example of use (shows commit authors in console):
```js run
(async () => {
let count = 0;
for await (const commit of fetchCommits('iliakan/javascript-tutorial-en')) {
console.log(commit.author.login);
if (++count == 100) { // let's stop at 100 commits
break;
}
}
})();
```
That's just what we wanted. The internal pagination mechanics is invisible from the outside. For us it's just an async generator that returns commits.
## Summary
Regular iterators and generators work fine with the data that doesn't take time to generate.
When we expect the data to come asynchronously, with delays, their async counterparts can be used, and `for await..of` instead of `for..of`.
Syntax differences between async and regular iterators:
| | Iterators | Async iterators |
|-------|-----------|-----------------|
| Object method to provide iteraterable | `Symbol.iterator` | `Symbol.asyncIterator` |
| `next()` return value is | any value | `Promise` |
Syntax differences between async and regular generators:
| | Generators | Async generators |
|-------|-----------|-----------------|
| Declaration | `function*` | `async function*` |
| `generator.next()` returns | `{value:…, done: true/false}` | `Promise` that resolves to `{value:…, done: true/false}` |
In web-development we often meet streams of data, when it flows chunk-by-chunk. For instance, downloading or uploading a big file.
We could use async generators to process such data, but there's also another API called Streams, that may be more convenient, as it provides special interfaces to transform the data and to pass it from one stream to another (e.g. download from one place and immediately send elsewhere). But they are also more complex.
Streams API not a part of Javascript language standard. Streams and async generators complement each other, both are great ways to handle async data flows.

View file

@ -0,0 +1,24 @@
<script>
async function* fetchCommits(repo) {
let url = `https://api.github.com/repos/${repo}/commits`;
while (url) {
const response = await fetch(url, {
headers: {'User-Agent': 'Our script'}, // github requires user-agent header
});
const body = await response.json(); // parses response as JSON (array of commits)
// the URL of the next page is in the headers, extract it
let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/);
nextPage = nextPage && nextPage[1];
url = nextPage;
// yield commits one by one, when they finish - fetch a new page url
for(let commit of body) {
yield commit;
}
}
}
</script>

2
1-js/11-async/index.md Normal file
View file

@ -0,0 +1,2 @@
# Promises, async/await