Intermediate · Async JavaScript, modules and tools · topic 42 of 64 · 35 min
async/await and error handling
Explanation
async and await
Put async before a function and two things change. First, it always returns a promise, even if you return a plain value. Second, you can use await inside it. await waits for a promise and gives you its value.
// with then
function loadTrains() {
return fetch('/api/trains')
.then((response) => response.json())
.then((data) => data.trains);
}
// the same with async/await
async function loadTrains() {
const response = await fetch('/api/trains');
const data = await response.json();
return data.trains; // the caller gets a promise of this
}Errors: try, catch, finally and throw
When an awaited promise rejects, await throws the error, just like a normal error. Catch it with try/catch. finally runs after either. Use throw new Error('clear message') to make your own errors.
async function showProfile(id) {
spinner.hidden = false;
try {
const response = await fetch(`/api/users/${id}`);
if (response.status === 404) throw new Error('This user does not exist');
if (!response.ok) throw new Error(`Server problem (HTTP ${response.status})`);
const user = await response.json();
nameEl.textContent = user.name;
} catch (error) {
errorEl.textContent = error.message;
} finally {
spinner.hidden = true; // always
}
}| Problem | What happens | How you notice |
|---|---|---|
| Network error (offline, wrong domain, CORS) | fetch rejects with a TypeError | Lands in catch by itself |
| HTTP error (404, 500 ...) | fetch fulfils, response.ok is false | Only if you check response.ok and throw |
| Bad JSON in the body | response.json() rejects with a SyntaxError | Lands in catch |
| Request cancelled or timed out | fetch rejects with AbortError or TimeoutError | Check error.name in catch |
One after another, or all together?
// Slow: about 1 s + 1 s + 1 s = 3 s
const user = await getJson('/api/user');
const cart = await getJson('/api/cart');
const offers = await getJson('/api/offers');
// Fast: all three start together, about 1 s in total
const [user2, cart2, offers2] = await Promise.all([
getJson('/api/user'),
getJson('/api/cart'),
getJson('/api/offers'),
]);Timeouts: do not wait forever
On a weak mobile network a request can hang for a long time. AbortSignal.timeout(ms) makes a signal that cancels the fetch after ms milliseconds. An AbortController lets you cancel it yourself, for example when the user types a new search.
try {
const response = await fetch('/api/pnr/1234567890', {
signal: AbortSignal.timeout(8000), // give up after 8 seconds
});
// ...
} catch (error) {
if (error.name === 'TimeoutError') showError('The server is slow. Please try again.');
else showError('Could not load your PNR status.');
}
// Cancel by hand: the old search is not needed any more
const controller = new AbortController();
fetch('/api/search?q=rice', { signal: controller.signal });
controller.abort(); // that fetch rejects with an AbortErrorRetry with a small delay
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function retry(fn, times, delayMs) {
for (let attempt = 1; attempt <= times; attempt++) {
try {
return await fn(); // success: stop here
} catch (error) {
if (attempt === times) throw error; // last try: give up
await wait(delayMs);
}
}
}
const data = await retry(() => getJson('/api/offers'), 3, 500);Top-level await in modules
In a module script (type="module", next topic) you can use await at the top level, outside any function. In a normal script, await only works inside an async function.
// main.js, loaded with <script type="module" src="main.js"></script>
const response = await fetch('/config.json');
const config = await response.json();
console.log('Shop name:', config.shopName);Watch the lesson
async/await and error handling · 1/7
async function load() {
const response = await fetch('/api/trains');
return response.json();
}async and await let promise code read like normal code, from top to bottom.
Prefer a video by a teacher? Find free videos on “javascript async await try catch tutorial” on YouTube ↗
In real life: Checking a PNR status on a slow network
A PNR status page calls the server with async/await inside try/catch/finally. It gives up after 8 seconds with AbortSignal.timeout and shows "The server is slow, try again". Because the network on a moving train is patchy, it retries a GET up to 3 times with a short wait. The passenger details and the train running status are loaded together with Promise.all, so the page is ready in one round of waiting, not two.
Practice (0/3 done)
Write async function getOrderTotal(fetchOrder, id). fetchOrder(id) returns a promise of an order like { id: "A1", items: [{ name: "Rice", price: 60, qty: 2 }, { name: "Dal", price: 40, qty: 1 }] }. Await it and return the total of price * qty for all items. For that order the answer is 160.
Write async function retry(fn, times, delayMs). fn is an async function that sometimes fails (throws). Call it; if it works, return its value at once. If it throws, wait delayMs milliseconds and try again, up to times tries in total. If every try fails, throw the LAST error. Example: a fn that fails twice and then returns "ok" gives "ok" with retry(fn, 3, 10), but throws with retry(fn, 2, 10).
Write withTimeout(fn, ms). It calls fn() (which returns a promise) and returns a promise that settles like fn() does, but rejects with new Error("Timeout") if fn() has not settled after ms milliseconds. Use Promise.race between fn() and a timer promise, and clear the timer at the end. Example: a job that takes 150 ms with withTimeout(job, 30) rejects with "Timeout".
Quick quiz
Read more (free)
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.