There are a few different angles I could take here!
Let's start with why they exist, follow up with what they do, and then go (briefly) into how they do it.
Why
All modern JavaScript runtimes (browser environments, Node.js, etc) use asynchronous I/O models. I/O operations (like calls to external APIs, or receiving user input) are characterized by waiting -- until your computer gets that API result, it can't do anything with it! Asynchronous I/O means that, while a function is "waiting" for an API result, the JavaScript runtime can start doing something else in the meantime. This improves app performance. But it's also harder to think about! We're taught how to program in a synchronous style, where line 1 executes, then line 2, then line 3. In an asynchronous style, maybe line 1 will execute, then line 6, then line 2....
There are a lot of different ways that JavaScript's language designers have tried to cope with this problem, like event emitters, callbacks, and promises. Async/await is the newest way of trying to make asynchronous code easier to think about.
What
Async/await looks like this:
functiongetBooksFromAPI(){returnfetch(BOOK_API_ENDPOINT);}asyncfunctiongetBookTitles(){try{constbookData=awaitgetBooksFromAPI();}catch{thrownewError("Book API unavailable");}returnbookData.map(book=>book.title);}
The first function returns a promise from fetch. This promise represents something the runtime intends to do -- it intends to get books from the API endpoint. Eventually, it will resolve to either success (book data) or failure. But it may or may not have done so yet. In the second function , we use the await keyword to wait for the promise to "fulfill". When the runtime hits an await, it knows to pause that function (and go do something else) until that promise has resolved itself.
When the promise resolves, the runtime "unpauses" the function. If the promise resolves with success, then the book data will get put into the bookData constant. If it fails, it'll throw a really non-specific error: Unhandled promise rejection. So we wrap it in a try/catch block to deal with that.*
try/catch isn't the only way to deal with Unhandled promise rejection errors. There are a lot of better ways! But it's the quickest to explain, and it's the most common.
Then, the runtime moves on to the next line, as though we were in synchronous code. In this case, we want to map the book data and return only the titles. Since await converted the API result promise (which isn't the API result, merely to the computer saying that it intends to get us an API result someday) into the actual book data, this is easy!
BUT! Because this function relies on awaiting something asynchronous (the API results) then it isn't going to return synchronously! So it needs to return a promise, too. The async keyword automatically wraps the return value of a function in a promise. If you're using await in a function, you need to mark it using the async keyword, so that the interpreter can trust that it returns a promise... because otherwise, you might try to call it synchronously and get unexpected results.
Ooof, that's a lot. and the how is even bigger.
How
This is something I don't really think can be done comprehensively in an ELI5 way. But the core of it is an idea called "generators," which let you specifically tell the JavaScript runtime "pause here until I call you next."
Since we "pause" every time we hit the yield keyword, the infinite loop is "safe" -- we can "unpause" with next, and know that we'll get the next number in the sequence when we "pause" again. The await keyword uses generators under the hood to ask promises whether they've resolved yet, so that you can "unpause" the parent function and move on to the next line.
More
I've written up a brief, free email course that goes deeper into what all of the asynchronous "stuff" in Javascript does, the history of it, and how to use it effectively -- that might be a good next place to go, if this explanation was useful to you!
A software engineer living in Edinburgh, Scotland. I'm passionate about programming, learning new things (and sharing what I learn), digital art, and good sci-fi/fantasy books! 💻📚🎨✨
There are a few different angles I could take here!
Let's start with why they exist, follow up with what they do, and then go (briefly) into how they do it.
Why
All modern JavaScript runtimes (browser environments, Node.js, etc) use asynchronous I/O models. I/O operations (like calls to external APIs, or receiving user input) are characterized by waiting -- until your computer gets that API result, it can't do anything with it! Asynchronous I/O means that, while a function is "waiting" for an API result, the JavaScript runtime can start doing something else in the meantime. This improves app performance. But it's also harder to think about! We're taught how to program in a synchronous style, where line 1 executes, then line 2, then line 3. In an asynchronous style, maybe line 1 will execute, then line 6, then line 2....
There are a lot of different ways that JavaScript's language designers have tried to cope with this problem, like event emitters, callbacks, and promises. Async/await is the newest way of trying to make asynchronous code easier to think about.
What
Async/await looks like this:
The first function returns a promise from fetch. This promise represents something the runtime intends to do -- it intends to get books from the API endpoint. Eventually, it will resolve to either success (book data) or failure. But it may or may not have done so yet. In the second function , we use the
await
keyword to wait for the promise to "fulfill". When the runtime hits anawait,
it knows to pause that function (and go do something else) until that promise has resolved itself.When the promise resolves, the runtime "unpauses" the function. If the promise resolves with success, then the book data will get put into the
bookData
constant. If it fails, it'll throw a really non-specific error:Unhandled promise rejection.
So we wrap it in atry/catch
block to deal with that.*try/catch
isn't the only way to deal withUnhandled promise rejection
errors. There are a lot of better ways! But it's the quickest to explain, and it's the most common.Then, the runtime moves on to the next line, as though we were in synchronous code. In this case, we want to map the book data and return only the titles. Since
await
converted the API result promise (which isn't the API result, merely to the computer saying that it intends to get us an API result someday) into the actual book data, this is easy!BUT! Because this function relies on
await
ing something asynchronous (the API results) then it isn't going to return synchronously! So it needs to return a promise, too. Theasync
keyword automatically wraps the return value of a function in a promise. If you're usingawait
in a function, you need to mark it using theasync
keyword, so that the interpreter can trust that it returns a promise... because otherwise, you might try to call it synchronously and get unexpected results.Ooof, that's a lot. and the how is even bigger.
How
This is something I don't really think can be done comprehensively in an ELI5 way. But the core of it is an idea called "generators," which let you specifically tell the JavaScript runtime "pause here until I call you next."
Since we "pause" every time we hit the yield keyword, the infinite loop is "safe" -- we can "unpause" with next, and know that we'll get the next number in the sequence when we "pause" again. The
await
keyword uses generators under the hood to ask promises whether they've resolved yet, so that you can "unpause" the parent function and move on to the next line.More
I've written up a brief, free email course that goes deeper into what all of the asynchronous "stuff" in Javascript does, the history of it, and how to use it effectively -- that might be a good next place to go, if this explanation was useful to you!
wecohere.com/products/untangling-a...
thanks a lot @Betsy . It's really awesome !!
This is a fantastic explanation!