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

Article: async functions #3617

Closed
wants to merge 8 commits into from
Closed

Conversation

jakearchibald
Copy link
Contributor

I haven't proof-read this myself, and I don't really have an ending yet, but here it is.


{% 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.
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

@addyosmani addyosmani Oct 6, 2016

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?

Copy link
Contributor Author

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}.

Copy link
Contributor

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

Copy link
Contributor Author

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.
Copy link
Contributor

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"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 resolves

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

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
Copy link
Contributor

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.

Copy link
Contributor Author

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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fucntion -> Function

Copy link
Contributor

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.

Copy link
Contributor Author

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) {
// …
}
Copy link
Contributor

@ebidel ebidel Oct 6, 2016

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.

Copy link
Contributor Author

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"`.
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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

Copy link
Contributor

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

Copy link
Contributor Author

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?

@jakearchibald
Copy link
Contributor Author

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.
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea

@jakearchibald jakearchibald changed the title (WIP) Article async functions Article: async functions Oct 7, 2016
@jakearchibald
Copy link
Contributor Author

jakearchibald commented Oct 7, 2016

Thanks for all the feedback everyone.

I'm pretty happy with this now. @addyosmani anyone else need to review this?

Copy link
Contributor

@ithinkihaveacat ithinkihaveacat left a 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
Copy link
Contributor

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?

Copy link
Contributor Author

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.

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 return Promise.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 that await uses. (async uses other techniques, e.g., that if it's followed by function, it would've been a syntax error.)

}

async function hello() {
await wait(500);
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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!

Copy link

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 😇

@jakearchibald
Copy link
Contributor Author

@ithinkihaveacat

Perhaps add a section on async/await gotchas? Also, are there any situations in which raw promises are preferable or easier to understand?

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.

Copy link
Member

@beaufortfrancois beaufortfrancois left a 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.
Copy link
Member

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?

Copy link
Contributor Author

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:
Copy link
Member

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.
Copy link
Member

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.

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.

Copy link

@littledan littledan left a 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) {
// …
}

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.

Copy link
Contributor Author

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

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 return Promise.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 that await uses. (async uses other techniques, e.g., that if it's followed by function, 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.

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.

Copy link
Contributor Author

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));

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)?

Copy link
Contributor Author

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);
}
}

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();
});

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) {

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").

Copy link
Contributor Author

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){:

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".

Copy link
Contributor Author

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.

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);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great example

@jakearchibald jakearchibald force-pushed the article-async-functions branch from 2955a16 to e89f6f9 Compare October 19, 2016 10:58
@jakearchibald
Copy link
Contributor Author

jakearchibald commented Oct 19, 2016

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.

petele added a commit that referenced this pull request Oct 19, 2016
Copy link
Member

@petele petele left a 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"
Copy link
Member

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){:
Copy link
Member

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.

Copy link
Contributor Author

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 nice use

@petele
Copy link
Member

petele commented Oct 19, 2016

Closing this and replacing with #3716 to more easily merge into master.

@petele petele closed this Oct 19, 2016
petele added a commit that referenced this pull request Oct 19, 2016
@petele petele deleted the article-async-functions branch October 19, 2016 15:59
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.