Skip to content

Commit

Permalink
Publish async/await!
Browse files Browse the repository at this point in the history
  • Loading branch information
iliakan committed May 25, 2017
1 parent fb5f39e commit 787d58a
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 45 deletions.
4 changes: 3 additions & 1 deletion 8-async/03-promise-chaining/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,9 @@ In theory, nothing should happen. In case of an error happens, the promise state

In practice, that means that the code is bad. Indeed, how come that there's no error handling?

Most JavaScript engines track such situations and generate a global error in that case. In the browser we can catch it using the event `unhandledrejection`:
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 it using the event `unhandledrejection`:

```js run
*!*
Expand Down
57 changes: 40 additions & 17 deletions 8-async/04-promise-api/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,29 +71,48 @@ The syntax is:
let promise = Promise.all(iterable);
```

It takes an `iterable` object with promises, for instance an array and returns a new promise that resolves with when all of them are settled and has an array of results.
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 the result is an array `[1, 2, 3]`:
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)),
new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 1000))
]).then(alert); // 1,2,3 after all promises are ready
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 more real-life example with fetching user information for an array of github users by their names:
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 names = ['iliakan', 'remy', 'jresig'];
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 = names.map(name => fetch(`https://api.github.com/users/${name}`));
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
Expand All @@ -103,13 +122,13 @@ Promise.all(requests)

return responses;
})
// map array of responses into array of response.json() and wrap them info Promise.all
// 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
// 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` also rejects with that error.
If any of the promises is rejected, `Promise.all` immediately rejects with that error.

For instance:

Expand All @@ -124,12 +143,14 @@ Promise.all([
]).catch(alert); // Error: Whoops!
```

Here the `.catch` runs after 2 seconds, when the second promise rejects, and the rejection error becomes the outcome of the whole `Promise.all`.
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.

The important detail is that promises provide no way to "cancel" or "abort" their execution. So in the example above all promises finally settle, but in case of an error all results are ignored, "thrown away".
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` wraps non-promise arguments into `Promise.resolve`"
Normally, `Promise.all(iterable)` accepts an iterable of promise objects. But if any of those objects is not a promise, it's wrapped in `Promise.resolve`.
````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]`:

Expand Down Expand Up @@ -167,7 +188,7 @@ Promise.race([
]).then(alert); // 1
```
So, the first result/error becomes the result of the whole `Promise.race`, and further results/errors are ignored.
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
Expand All @@ -177,3 +198,5 @@ There are 4 static methods of `Promise` class:
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.
49 changes: 49 additions & 0 deletions 8-async/05-async-await/01-rewrite-async-2/solution.md
Original file line number Diff line number Diff line change
@@ -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();
```
48 changes: 48 additions & 0 deletions 8-async/05-async-await/01-rewrite-async-2/task.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

# Rewrite "rethrow" async/await

Rewrite the "rethrow" example from the chapter <info:promise-chaining> using `async/await` instead of `.then/catch`.

And get rid of recursion in favour of a loop in `demoGithubUser`: with `async/await` that becomes possible and is easier to develop later on.

```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();
```
33 changes: 33 additions & 0 deletions 8-async/05-async-await/01-rewrite-async/solution.md
Original file line number Diff line number Diff line change
@@ -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.
20 changes: 20 additions & 0 deletions 8-async/05-async-await/01-rewrite-async/task.md
Original file line number Diff line number Diff line change
@@ -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
```
Loading

0 comments on commit 787d58a

Please sign in to comment.