Skip to content
This repository was archived by the owner on Aug 10, 2022. It is now read-only.

Commit 58dd6a6

Browse files
authored
Merge pull request #3716 from google/article-async-functions-2
async article - replaces #3617
2 parents 99e5469 + 501860d commit 58dd6a6

File tree

2 files changed

+342
-1
lines changed

2 files changed

+342
-1
lines changed

src/content/en/fundamentals/getting-started/_toc.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ toc:
2525
path: /web/fundamentals/getting-started/primers/service-workers
2626
- title: "JavaScript Promises: an Introduction"
2727
path: /web/fundamentals/getting-started/primers/promises
28+
- title: JavaScript Async Functions
29+
path: /web/fundamentals/getting-started/primers/async-functions
2830
- title: "Shadow DOM v1: Self-Contained Web Components"
2931
path: /web/fundamentals/getting-started/primers/shadowdom
3032
- title: "Custom Elements v1: Reusable Web Components"
3133
path: /web/fundamentals/getting-started/primers/customelements
32-
- title: "Payment Request API"
34+
- title: Payment Request API
3335
section:
3436
- title: Integration Guide
3537
path: /web/fundamentals/getting-started/primers/payment-request/
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
project_path: /web/_project.yaml
2+
book_path: /web/fundamentals/_book.yaml
3+
description: Async functions allow you to write promise-based code as if it were synchronous
4+
5+
{# wf_published_on: 2016-10-20 #}
6+
{# wf_updated_on: 2016-10-20 #}
7+
8+
# Async functions - making promises friendly {: .page-title }
9+
10+
{% include "web/_shared/contributors/jakearchibald.html" %}
11+
12+
Async functions are enabled by default in Chrome 55 and they're quite frankly
13+
marvelous. They allow you to write promise-based code as if it were synchronous,
14+
but without blocking the main thread. They make your asynchronous code less
15+
"clever" and more readable.
16+
17+
Async functions work like this:
18+
19+
async function myFirstAsyncFunction() {
20+
try {
21+
const fulfilledValue = await promise;
22+
}
23+
catch (rejectedValue) {
24+
// …
25+
}
26+
}
27+
28+
If you use the `async` keyword before a function definition, you can then use
29+
`await` within the function. When you `await` a promise, the function is paused
30+
until the promise settles. If the promise fulfills, you get the value back. If
31+
the promise rejects, the rejected value is thrown.
32+
33+
Note: If you're unfamiliar with promises, check out [our
34+
promises guide](/web/fundamentals/getting-started/primers/promises).
35+
36+
## Example: Logging a fetch
37+
38+
Say we wanted to fetch a URL and log the response as text. Here's how it looks
39+
using promises:
40+
41+
function logFetch(url) {
42+
return fetch(url)
43+
.then(response => response.text())
44+
.then(text => {
45+
console.log(text);
46+
}).catch(err => {
47+
console.error('fetch failed', err);
48+
});
49+
}
50+
51+
And here's the same thing using async functions:
52+
53+
async function logFetch(url) {
54+
try {
55+
const response = await fetch(url);
56+
console.log(await response.text());
57+
}
58+
catch (err) {
59+
console.log('fetch failed', err);
60+
}
61+
}
62+
63+
It's the same number of lines, but all the callbacks are gone. This makes it way
64+
easier to read, especially for those less familiar with promises.
65+
66+
## Async return values
67+
68+
Calling an async function returns a promise for whatever the function returns or
69+
throws. So with:
70+
71+
// wait ms milliseconds
72+
function wait(ms) {
73+
return new Promise(r => setTimeout(r, ms));
74+
}
75+
76+
async function hello() {
77+
await wait(500);
78+
return 'world';
79+
}
80+
81+
…calling `hello()` returns a promise that *fulfills* with `"world"`.
82+
83+
async function foo() {
84+
await wait(500);
85+
throw Error('bar');
86+
}
87+
88+
…calling `foo()` returns a promise that *rejects* with `Error('bar')`.
89+
90+
## Example: Streaming a response
91+
92+
The benefit of async functions increases in more complex examples. Say we wanted
93+
to stream a response while logging out the chunks, and return the final size.
94+
95+
Note: The phrase "logging out the chunks" made me sick in my mouth.
96+
97+
Here it is with promises:
98+
99+
function getResponseSize(url) {
100+
return fetch(url).then(response => {
101+
const reader = response.body.getReader();
102+
let total = 0;
103+
104+
return reader.read().then(function processResult(result) {
105+
if (result.done) return total;
106+
107+
const value = result.value;
108+
total += value.length;
109+
console.log('Received chunk', value);
110+
111+
return reader.read().then(processResult);
112+
})
113+
});
114+
}
115+
116+
Check me out, Jake "wielder of promises" Archibald. See how I'm calling
117+
`processResult` inside itself to set up an asynchronous loop? Writing that made
118+
me feel *very smart*. But like most "smart" code, you have to stare at it for
119+
ages to figure out what it's doing, like one of those magic-eye pictures from
120+
the 90's.
121+
122+
Let's try that again with async functions:
123+
124+
async function getResponseSize(url) {
125+
const response = await fetch(url);
126+
const reader = response.body.getReader();
127+
let result = await reader.read();
128+
let total = 0;
129+
130+
while (!result.done) {
131+
const value = result.value;
132+
total += value.length;
133+
console.log('Received chunk', value);
134+
// get the next result
135+
result = await reader.read();
136+
}
137+
138+
return total;
139+
}
140+
141+
All the "smart" is gone. The asynchronous loop that made me feel so smug is
142+
replaced with a trusty, boring, while-loop. Much better. In future, we'll get
143+
[async iterators](https://github.com/tc39/proposal-async-iteration){: .external},
144+
which would
145+
[replace the `while` loop with a for-of loop](https://gist.github.com/jakearchibald/0b37865637daf884943cf88c2cba1376){: .external}, making it even neater.
146+
147+
Note: I'm sort-of in love with streams. If you're unfamiliar with streaming,
148+
[check out my guide](https://jakearchibald.com/2016/streams-ftw/#streams-the-fetch-api){: .external}.
149+
150+
## Other async function syntax
151+
152+
We've seen `async function() {}` already, but the `async` keyword can be used
153+
with other function syntax:
154+
155+
### Arrow functions
156+
157+
// map some URLs to json-promises
158+
const jsonPromises = urls.map(async url => {
159+
const response = await fetch(url);
160+
return response.json();
161+
});
162+
163+
Note: `array.map(func)` doesn't care that I gave it an async function, it just
164+
sees it as a function that returns a promise. It won't wait for the first
165+
function to complete before calling the second.
166+
167+
### Object methods
168+
169+
const storage = {
170+
async getAvatar(name) {
171+
const cache = await caches.open('avatars');
172+
return cache.match(`/avatars/${name}.jpg`);
173+
}
174+
};
175+
176+
storage.getAvatar('jaffathecake').then(…);
177+
178+
### Class methods
179+
180+
class Storage {
181+
constructor() {
182+
this.cachePromise = caches.open('avatars');
183+
}
184+
185+
async getAvatar(name) {
186+
const cache = await this.cachePromise;
187+
return cache.match(`/avatars/${name}.jpg`);
188+
}
189+
}
190+
191+
const storage = new Storage();
192+
storage.getAvatar('jaffathecake').then(…);
193+
194+
Note: Class constructors and getters/settings cannot be async.
195+
196+
## Careful! Avoid going too sequential
197+
198+
Although you're writing code that looks synchronous, ensure you don't miss the
199+
opportunity to do things in parallel.
200+
201+
async function series() {
202+
await wait(500);
203+
await wait(500);
204+
return "done!";
205+
}
206+
207+
The above takes 1000ms to complete, whereas:
208+
209+
async function parallel() {
210+
const wait1 = wait(500);
211+
const wait2 = wait(500);
212+
await wait1;
213+
await wait2;
214+
return "done!";
215+
}
216+
217+
…the above takes 500ms to complete, because both waits happen at the same time.
218+
Let's look at a practical example…
219+
220+
### Example: Outputting fetches in order
221+
222+
Say we wanted to fetch a series URLs and log them as soon as possible, in the
223+
correct order.
224+
225+
*Deep breath* - here's how that looks with promises:
226+
227+
function logInOrder(urls) {
228+
// fetch all the URLs
229+
const textPromises = urls.map(url => {
230+
return fetch(url).then(response => response.text());
231+
});
232+
233+
// log them in order
234+
textPromises.reduce((chain, textPromise) => {
235+
return chain.then(() => textPromise)
236+
.then(text => console.log(text));
237+
}, Promise.resolve());
238+
}
239+
240+
Yeah, that's right, I'm using `reduce` to chain a sequence of promises. I'm *so
241+
smart*. But this is a bit of *so smart* coding we're better off without.
242+
243+
However, when converting the above to an async function, it's tempting to go
244+
*too sequential*:
245+
246+
<span class="compare-worse">Not recommended</span> - too sequential
247+
248+
async function logInOrder(urls) {
249+
for (const url of urls) {
250+
const response = await fetch(url);
251+
console.log(await response.text());
252+
}
253+
}
254+
255+
Looks much neater, but my second fetch doesn't begin until my first fetch has
256+
been fully read, and so on. This is much slower than the promises example that
257+
performs the fetches in parallel. Thankfully there's an ideal middle-ground:
258+
259+
<span class="compare-better">Recommended</span> - nice and parallel
260+
261+
async function logInOrder(urls) {
262+
// fetch all the URLs in parallel
263+
const textPromises = urls.map(async url => {
264+
const response = await fetch(url);
265+
return response.text();
266+
});
267+
268+
// log them in sequence
269+
for (const textPromise of textPromises) {
270+
console.log(await textPromise);
271+
}
272+
}
273+
274+
In this example, the URLs are fetched and read in parallel, but the "smart"
275+
`reduce` bit is replaced with a standard, boring, readable for-loop.
276+
277+
## Browser support & workarounds
278+
279+
At time of writing, async functions are enabled by default in Chrome 55, but
280+
they're being developed in all the main browsers:
281+
282+
* Edge - [In build 14342+ behind a flag](https://developer.microsoft.com/en-us/microsoft-edge/platform/status/asyncfunctions/)
283+
* Firefox - [active development](https://bugzilla.mozilla.org/show_bug.cgi?id=1185106)
284+
* Safari - [active development](https://bugs.webkit.org/show_bug.cgi?id=156147)
285+
286+
### Workaround - Generators
287+
288+
If you're targeting browsers that support generators (which includes
289+
[the latest version of every major browser](http://kangax.github.io/compat-table/es6/#test-generators){:.external}
290+
) you can sort-of polyfill async functions.
291+
292+
[Babel](https://babeljs.io/){: .external} will do this for you,
293+
[here's an example via the Babel REPL](https://goo.gl/0Cg1Sq){: .external}
294+
- note how similar the transpiled code is. This transformation is part of
295+
[Babel's stage 3 preset](http://babeljs.io/docs/plugins/preset-stage-3/){: .external}.
296+
297+
Note: Babel REPL is fun to say. Try it.
298+
299+
I recommend the transpiling approach, because you can just turn it off once your
300+
target browsers support async functions, but if you *really* don't want to use a
301+
transpiler, you can take
302+
[Babel's polyfill](https://gist.github.com/jakearchibald/edbc78f73f7df4f7f3182b3c7e522d25){: .external}
303+
and use it yourself. Instead of:
304+
305+
async function slowEcho(val) {
306+
await wait(1000);
307+
return val;
308+
}
309+
310+
…you'd include [the polyfill](https://gist.github.com/jakearchibald/edbc78f73f7df4f7f3182b3c7e522d25){: .external}
311+
and write:
312+
313+
const slowEcho = createAsyncFunction(function*(val) {
314+
yield wait(1000);
315+
return val;
316+
});
317+
318+
Note that you have to pass a generator (`function*`) to `createAsyncFunction`,
319+
and use `yield` instead of `await`. Other than that it works the same.
320+
321+
### Workaround - regenerator
322+
323+
If you're targeting older browsers, Babel can also transpile generators,
324+
allowing you to use async functions all the way down to IE8. To do this you need
325+
[Babel's stage 3 preset](http://babeljs.io/docs/plugins/preset-stage-3/){: .external}
326+
*and* the [ES2015 preset](http://babeljs.io/docs/plugins/preset-es2015/){: .external}.
327+
328+
The [output is not as pretty](https://goo.gl/jlXboV), so watch out for
329+
code-bloat.
330+
331+
## Async all the things!
332+
333+
Once async functions land across all browsers, use them on every
334+
promise-returning function! Not only do they make your code tider, but it makes
335+
sure that function will *always* return a promise.
336+
337+
I got really excited about async functions [back in
338+
2014](https://jakearchibald.com/2014/es7-async-functions/){: .external}, and
339+
it's great to see them land, for real, in browsers. Whoop!

0 commit comments

Comments
 (0)