-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Conversation
|
||
{% include "web/_shared/contributors/jakearchibald.html" %} | ||
|
||
Async functions are enabled by default in Chrome 55 and they're quite frankly marvelous. They allow you to write promise-based code as if it were synchronous, but without blocking the main thread. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Async functions
link to spec or MDN page?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure about this one. The spec is kinda scary and there's no MDN page yet. Even then, would we want to link to an explaining article this early on in our explaining article?
### Workaround - regenerator | ||
|
||
If you're targeting older browsers, Babel can also transpile generators, allowing you to use async functions all the way down to IE8. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for calling out the path to older browser support here too. Link to Babel docs for transpiling generators here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah good point
## Example: Streaming a response | ||
|
||
Note: If you're unfamiliar with streaming, [check out my guide](https://jakearchibald.com/2016/streams-ftw/#streams-the-fetch-api){: .external}. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You just had to mention streams :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep on streamin'
} | ||
} | ||
|
||
If you use the `async` keyword before a function definition, you can then use `await` within the function. When you `await` a promise, the function is paused until the promise settles. If the promise fulfills, you get the value back. If the promise rejects, the rejected value is thrown. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nit, but do you want to say "resolves" instead of "fulfills"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 resolves
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The opposite of reject is "fulfill". A promise can resolve to a rejected promise, eg Promise.resolve(Promise.reject(new Error("foo")))
resolves to a rejected promise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out I wrote about it https://jakearchibald.com/2014/resolve-not-opposite-of-reject/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interetesting. TIL.
For anyone else seeing this, I also found this helpful:
https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md
|
||
Note: The phrase "logging out the chunks" made me sick in my mouth. | ||
|
||
## Example: Avoid going too linear |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know what you meant by "too linear" when first reading this heading. It took me reading the examples to figure it out.
I wonder if sequential is a better word.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, much better
|
||
Async functions look like this: | ||
|
||
async function myFirstAsyncFucntion() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fucntion -> Function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol - I am rubbing off on Jake. I said this to him yesterday that I can write function properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hah, yep. Good catch.
} | ||
catch (rejectedValue) { | ||
// … | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so because async functions always return a promise, you don't explicitly need a return val? Might want to mention that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Welllllll you need to return what you want the promise to resolve to, else it resolves to void
the same way functions return void if you don't have an explicit return.
return 'world'; | ||
} | ||
|
||
…calling `hello()` returns a promise that *fulfills* with `"world"`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would be good to show the examples of the calls for each of these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea
this.cachePromise = caches.open('avatars'); | ||
} | ||
|
||
async getAvatar(name) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
show an example usage of getAvatar()
In this example, the URLs are fetched and read as text in parallel, but the "smart" `reduce` bit is replaced with a standard, readable, for-loop. | ||
|
||
## Browser support & workarounds | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about using a framebox here to embed the chromestatus info:
https://www.chromestatus.com/feature/5643236399906816?embed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this a bit huge for an embed?
Oh, I need to wrap all the lines too. Oops. |
} | ||
} | ||
|
||
If you use the `async` keyword before a function definition, you can then use `await` within the function. When you `await` a promise, the function is paused until the promise settles. If the promise fulfills, you get the value back. If the promise rejects, the rejected value is thrown. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use this space to explicitly call out that top-level await
isn't supported, and that the only time you can use await
is inside an async
function.
That's something that I found non-obvious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea
Thanks for all the feedback everyone. I'm pretty happy with this now. @addyosmani anyone else need to review this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps add a section on async/await gotchas? Also, are there any situations in which raw promises are preferable or easier to understand?
} | ||
} | ||
|
||
If you use the `async` keyword before a function definition, you can then use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe not for this article, but why is async
even necessary? (Why is await
appearing in the body not sufficient?) Is this a convenience to the JS engine?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the reasons are similar to why function()*
is necessary for generators, but I'm not 100% sure.
FWIW I've been caught out by generators in Python in the past. It wasn't clear to me that a function would behave very differently because it has a yield
hidden away in it somewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few reasons:
- When you have such drastically different modes, it's a benefit to readability to have it called out when the function starts (as Jake says above).
- You can have an async function which doesn't contain any await; the semantics will be kinda sorta like if
return foo
was returnPromise.resolve(foo)
await
is a new keyword, but we can't "just" add keywords. They might already be used as variables. Scoping the keyword-ness to async functions is one technique thatawait
uses. (async
uses other techniques, e.g., that if it's followed byfunction
, it would've been a syntax error.)
} | ||
|
||
async function hello() { | ||
await wait(500); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth explaining why adding an additional await wait(500)
will result in this function taking 1s to run, and why something like
const p1 = wait(500);
const p2 = wait(500);
const v1 = await p1;
const v2 = await p2;
(which seems more or less equivalent) takes 0.5s.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohh, this is a good example. Let me see if I can work it in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this at the start of the series vs parallel bit. Cheers!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might like this post 😇
The biggest gotcha is forgetting that you're async, or making thinks too sequential, which I cover. Generally it's ok to use async functions for everything you'd previously use promises for. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great article ;) See my 2 cents!
const storage = new Storage(); | ||
storage.getAvatar('jaffathecake').then(…); | ||
|
||
Note: Class constructors cannot be async. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get
and set
as well. However it looks like static
works fine.
Shall we be exhaustive there @jakearchibald?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeahhhh why not
ages to figure out what it's doing, like one of those magic-eye pictures from | ||
the 90's. | ||
|
||
Let's try that again with async functions: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we also add the catch
to show how to handle errors?
return "done!"; | ||
} | ||
|
||
…the above takes 500ms to complete, because both waits happen at the same time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may want to call out observables
are different than promises
as they always start no matter what when you call them unlike observables
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why observables would be mentioned here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great article, seems to get at all the right points. I think the beginning could be changed a little bit to lead with examples, rather than an explanation of obscure error handling.
} | ||
catch (rejectedValue) { | ||
// … | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see a lot of code samples like this, with the try/catch. I worry that presenting async functions like this may make people think they always need a try/catch block inside their async function body, which definitely isn't true. Maybe the initial example should be a chain of awaiting multiple things, or even a loop, and then explain the exceptions/rejection duality later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try and pick up on this elsewhere in the article. I kinda like opening with how fulfill/reject values map in async functions.
} | ||
} | ||
|
||
If you use the `async` keyword before a function definition, you can then use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few reasons:
- When you have such drastically different modes, it's a benefit to readability to have it called out when the function starts (as Jake says above).
- You can have an async function which doesn't contain any await; the semantics will be kinda sorta like if
return foo
was returnPromise.resolve(foo)
await
is a new keyword, but we can't "just" add keywords. They might already be used as variables. Scoping the keyword-ness to async functions is one technique thatawait
uses. (async
uses other techniques, e.g., that if it's followed byfunction
, it would've been a syntax error.)
If you use the `async` keyword before a function definition, you can then use | ||
`await` within the function. When you `await` a promise, the function is paused | ||
until the promise settles. If the promise fulfills, you get the value back. If | ||
the promise rejects, the rejected value is thrown. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the right paragraph to lead with? Maybe the article should open with an explanation and examples of, "you can await a bunch of stuff in a row!" and then get into the nitty gritty of exception handling later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't really feel nitty gritty to me, it's kinda "the basics". I'm going to move the stuff about return values to after the first example, so readers will get to the first example quicker.
|
||
// wait ms milliseconds | ||
function wait(ms) { | ||
return new Promise(r => setTimeout(r, ms)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally, I like this example because it shows a simple async/await usage without exceptions. However, could we open with something that uses platform-provided Promises, rather than the Promise constructor (whose usage, long-term, should be minimal)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, moving this to after the first example
catch (err) { | ||
console.log('fetch failed', err); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome example, much cleaner and gives a good use case for exception handling/rejection. Maybe you should move that opening try/catch example to just above this.
const jsonPromises = urls.map(async url => { | ||
const response = await fetch(url); | ||
return response.json(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another cool thing you can do here is Promise.all(foo.map(async elt => fn(elt)))
.
let result; | ||
let total = 0; | ||
|
||
while ((result = await reader.read()) && !result.done) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how this uses a loop, and how it uses a bunch of await calls, and seems really practical. However, maybe it would be easier to read if you didn't use the pattern of assignment &&
another expression, instead breaking it out into another line (otherwise it still may have the smell of "wielder of something").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeahhhhh fair enough. Means having reader.read
twice in the code, but I guess that's why async iterables are needed.
replaced with a trusty, boring, while-loop. Much better. In future, we'll get | ||
[async iterators](https://github.com/tc39/proposal-async-iteration){: | ||
.external}, which would [replace the above `while` | ||
loop](https://gist.github.com/jakearchibald/0b37865637daf884943cf88c2cba1376){: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could write inline, "would replace the while loop with a for-of loop".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall do
return "done!"; | ||
} | ||
|
||
…the above takes 500ms to complete, because both waits happen at the same time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why observables would be mentioned here.
// log them in sequence | ||
for (const textPromise of textPromises) { | ||
console.log(await textPromise); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great example
2955a16
to
e89f6f9
Compare
I've reordered the article a little so readers get to the examples quicker, and made some minor updates based on feedback. I'm happy for this to be merged btw. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few minor comments only to provide feedback for future articles.
@@ -25,6 +25,8 @@ toc: | |||
path: /web/fundamentals/getting-started/primers/service-workers | |||
- title: "JavaScript Promises: an Introduction" | |||
path: /web/fundamentals/getting-started/primers/promises | |||
- title: "JavaScript Async Functions" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You only need to quote if you're using a 'fun' character like :
|
||
All the "smart" is gone. The asynchronous loop that made me feel so smug is | ||
replaced with a trusty, boring, while-loop. Much better. In future, we'll get | ||
[async iterators](https://github.com/tc39/proposal-async-iteration){: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't break {: something }
attributes, the parser isn't smart enough to handle this. :( keep them all on one line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Damnit, that's what my auto-wrapping plugin does. Will watch out for this, cheers!
been fully read, and so on. This is much slower than the promises example that | ||
performs the fetches in parallel. Thankfully there's an ideal middle-ground: | ||
|
||
<span class="compare-better">Recommended</span> - nice and parallel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 nice use
Closing this and replacing with #3716 to more easily merge into master. |
I haven't proof-read this myself, and I don't really have an ending yet, but here it is.