JavaScript never blocks: network requests, timers and animations run asynchronously. This lesson covers callbacks, Promises and async/await, and how to handle errors in asynchronous code.
Important
Work on this lesson in
buildermode.
Learning goals
- You can explain why JavaScript is single-threaded and non-blocking, and what the event loop does with tasks and microtasks.
- You can explain the difference between synchronous and asynchronous APIs, and why a network request cannot simply return its response.
- You can work with callbacks, Promises and
async/await, and translate between the three styles. - You can explain the states of a Promise, how
then/catch/finallychain, and how a rejection propagates through a chain. - You can handle errors in asynchronous code, including in event listeners, and explain why throwing from an
asyncfunction is a trap. - You can run several asynchronous operations in parallel and continue when all of them are done.
- You can explain why asynchrony spreads through a call chain ("colored functions") and what that means for designing an API.
- You know the browser's scheduling APIs — timers,
requestAnimationFrame,requestIdleCallback— and when each fits.
Resources
Read what's new to you, skim what's familiar, skip what you already master. Stop when you can meet the learning goals.
Your agent can also generate an overview, a tutorial or an explanation for anything here, tailored to what you already know. Just ask.
Start here
- 📄 Async control flow in JavaScript: Promises, microtasks, async/await — Henning's presentation; the German video Show archive.org snapshot is in our library
- ▶️ JavaScript Visualized: Event loop, Web APIs, (micro)task queue Show archive.org snapshot — Lydia Hallie; the best 15 minutes on why JavaScript never blocks
Promises and async/await
- 📄 MDN:
Promise
Show archive.org snapshot
,
async functionShow archive.org snapshot andawaitShow archive.org snapshot - ▶️ JavaScript Visualized: Promise execution Show archive.org snapshot — Lydia Hallie, 2024; also as an illustrated article Show archive.org snapshot
- 📄 Don't throw synchronous exceptions from functions that return Promises — our card
- 📄 Canceling promises — our card
- 📄 Error handling in DOM event listeners — our card
- 📄 What color is your function? Show archive.org snapshot — why asynchrony spreads through a call chain
The event loop in depth
- 📄 Tasks, microtasks, queues and schedules Show archive.org snapshot — Jake Archibald's interactive explanation
- 📄
Picking the right tool for maneuvering JavaScript's event loop
Show archive.org snapshot
— timers,
requestAnimationFrame,requestIdleCallback, microtasks - 📄 MDN:
requestAnimationFrameShow archive.org snapshot andrequestIdleCallbackShow archive.org snapshot ; see also unnecessary repaints in our rendering-performance card
Questions to answer while reading
- What are "synchronous" or "blocking" APIs versus "asynchronous" APIs?
- Why does
fetch()not simply return the response from the server? - Why is asynchronous code such a big issue in JavaScript (as opposed to the code we usually write in Ruby)?
- What are "callbacks" in JavaScript? Can you find examples for libraries that use callbacks to deal with asynchronicity?
- What are "promises" in JavaScript? Can you find examples for libraries that use promises to deal with asynchronicity?
- How can you parallelize multiple calls of an
asyncfunction?
Exercises
Judging asynchronous code
Look at the following code:
async function init() {
console.log("first")
await helperFunction()
console.log("fourth")
}
async function helperFunction() {
console.log("second")
await backgroundTask()
console.log("third")
}
// Stub function that resolves after 100 milliseconds, could be an API call
function backgroundTask() {
return new Promise((resolve) => setTimeout(resolve, 100))
}
init()
What happens when you remove the await of helperFunction? What happens when you remove the await of init? What causes the new behavior?
Now have a look at this sketched usage of asynchronous functions:
const news = await fetchNews()
renderNews(news)
const recentUsers = await fetchRecentUsers()
renderUsers(recentUsers)
Do you agree with the implementation? If not, what could be improved?
atFullSecond()
Create an implementation of setTimeout that returns a promise rather than taking a callback. Name this new function atFullSecond, as it should wait for the remaining milliseconds until the next second starts. For example, at 13:23 and 441 milliseconds, atFullSecond(1) should wait for 559 milliseconds before triggering the callback function.
Internally, it can make use of setTimeout, but the API should be like that:
atFullSecond(2).then(function() {
console.log(`waited until exactly ${new Date}`)
})
new Date().getUTCMilliseconds() gives you access to the current millisecond offset.
As a bonus, also reject the promise if the caller passes number smaller than one to atFullSecond.
doubleRandom()
Write a function called doubleRandom that
- fetches and logs two random numbers from an API
- returns the sum of those two numbers
You can either implement your own API backend or use the free service at api.random.org Show archive.org snapshot . If you chose the the latter, create an account for it and click yourself a "Developer" API key. Then head over to the documentation of the generateIntegers endpoint Show archive.org snapshot . As learning JSON-RPC is out of scope of this card, you may base your client-side implementation on this code:
fetch('https://api.random.org/json-rpc/4/invoke', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '4.0',
method: 'generateIntegers',
params: {
apiKey: ...,
// other params described in the API documentation such as "n", "min" and "max"
}
})
fetch()
Write a function that:
- Fetches a list of records via
fetch(). For example, fetch the list of our open-source repos from https://api.github.com/users/makandra/repos Show archive.org snapshot . - Inserts the list into the DOM.
- Slowly fades in the list.
- Allows its caller to run code when the fade-in animation is done.
Create three different implementations of your function:
- Using callbacks
- Using promises and
then() - Using promises and
async/await
Note that you are expected to use fetch in every variant, even though it does not offer a callback interface.