Warm-up · Activity 1 of 7
Warm-up: what does calling an async function give you?
async function price() {
return 5;
}
console.log(price());// I1.3 · ~33 min · Intermediate
After this lesson you write promise code with async and await that reads top to bottom, catch its errors with try/catch/finally, and start independent steps together instead of one by one.
You will be able to
Warm-up · Activity 1 of 7
async function price() {
return 5;
}
console.log(price());Predict · Activity 2 of 7
const later = (value, ms) => new Promise((resolve) => setTimeout(() => resolve(value), ms));
async function main() {
console.log(await later("a", 30));
console.log(await later("b", 10));
}
main();
console.log("main called");Practice · Activity 3 of 7
____ function load() {
const value = await Promise.resolve("data");
return value;
}Practice · Activity 4 of 7
async function save() {
try {
await Promise.reject(new Error("disk full"));
console.log("saved");
} catch (error) {
console.log("error:", error.message);
} finally {
console.log("cleanup");
}
}
save();Practice · Activity 5 of 7
Brain teaser · Activity 6 of 7
async function isAdmin(user) {
return user === "root";
}
if (isAdmin("ada")) {
console.log("welcome, admin");
} else {
console.log("welcome, user");
}Apply · Activity 7 of 7
Check your work against this list
Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.
Worked example
step() logs when it starts and ends and settles after a delay. oneByOne() awaits a, then b: b starts only after a has ended. together() starts c and d at once and awaits Promise.all, so the faster d ends first, yet the results keep their order. safely() awaits a step that fails inside try: catch and finally run. main.js is a module, so it awaits at the top level.
main.js
// A step that logs when it starts and ends, and settles after ms milliseconds.
const step = (name, ms, fail = false) =>
new Promise((resolve, reject) => {
console.log(`start ${name}`);
setTimeout(() => {
console.log(`end ${name}`);
if (fail) reject(new Error(`${name} failed`));
else resolve(name.toUpperCase());
}, ms);
});
async function oneByOne() {
const a = await step("a", 30); // b does not start until a has ended
const b = await step("b", 10);
return [a, b];
}
async function together() {
const [c, d] = await Promise.all([step("c", 30), step("d", 10)]); // both start now
return [c, d];
}
async function safely() {
try {
await step("e", 5, true);
console.log("not reached");
} catch (error) {
console.log("caught:", error.message);
} finally {
console.log("finally runs");
}
}
console.log(await oneByOne()); // top-level await: main.js is a module
console.log(await together());
await safely();
Run it with
node main.jsOutput
start a
end a
start b
end b
[ 'A', 'B' ]
start c
start d
end d
end c
[ 'C', 'D' ]
start e
end e
caught: e failed
finally runsTab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.
Exercise 1 of 2
profileLine(api) returns a promise of a line such as Ada (3 posts). api.user() gives {id, name}; api.posts(id) gives an array of posts. If api.posts rejects, the line is Ada (posts unavailable); if api.user rejects, profileLine rejects with that error. The starter forgets one await and so prints undefined posts and never catches. Fix it with await and try/catch.
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.
Run the tests: the first one says undefined posts. What is posts without await?
A promise has no length, and a rejection that is never awaited inside the try cannot reach the catch.
Write const posts = await api.posts(user.id); inside the try.
One way to solve it. Yours can look different and still pass the checks.
export async function profileLine(api) {
const user = await api.user();
try {
const posts = await api.posts(user.id);
return `${user.name} (${posts.length} posts)`;
} catch {
return `${user.name} (posts unavailable)`;
}
}
Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.
main.js
export async function profileLine(api) {
const user = await api.user();
try {
const posts = api.posts(user.id);
return `${user.name} (${posts.length} posts)`;
} catch {
return `${user.name} (posts unavailable)`;
}
}
main.test.js
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {profileLine} from './main.js';
const later = (value, fail = false) =>
new Promise((resolve, reject) => setTimeout(() => (fail ? reject(value) : resolve(value)), 5));
test('Ada with three posts gives Ada (3 posts)', async () => {
const api = {user: () => later({id: 1, name: 'Ada'}), posts: () => later(['a', 'b', 'c'])};
const got = await profileLine(api);
assert.equal(got, 'Ada (3 posts)', `profileLine gave ${got}`);
});
test('Failing posts give Ada (posts unavailable)', async () => {
const api = {user: () => later({id: 1, name: 'Ada'}), posts: () => later(new Error('timeout'), true)};
const got = await profileLine(api);
assert.equal(got, 'Ada (posts unavailable)', `profileLine gave ${got}`);
});
test('A failing user rejects with the same error', async () => {
const api = {user: () => later(new Error('no user'), true), posts: () => later([])};
await assert.rejects(profileLine(api), {message: 'no user'}, 'profileLine should reject with the user error');
});
package.json
{
"type": "module"
}
package.json tells Node.js that the .js files are modules; keep it in the folder.
Run the program:
node main.jsRun the checks (needs learnrun.js in the same folder):
node --testDownload learnrun.jsExercise 2 of 2
loadAll(loaders) gets an array of functions; each returns a promise. It must return a promise of all their results, in the same order. The starter awaits each loader in a loop, so each one starts only after the one before has finished. The results are right, but slow. Start every loader first, then wait for all of them together.
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.
Run the tests: the log shows start 0, end 0, start 1. Where does the loop wait?
Call every loader first: loaders.map((load) => load()) gives an array of promises that are already running.
Then wait for all of them at once with await Promise.all(…), which keeps their order.
One way to solve it. Yours can look different and still pass the checks.
export async function loadAll(loaders) {
return Promise.all(loaders.map((load) => load()));
}
Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.
main.js
export async function loadAll(loaders) {
const results = [];
for (const load of loaders) {
results.push(await load());
}
return results;
}
main.test.js
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {loadAll} from './main.js';
// Each loader logs when it starts and ends; later ones are faster.
const makeLoaders = (log) =>
[30, 20, 10].map((ms, i) => () => {
log.push(`start ${i}`);
return new Promise((resolve) => setTimeout(() => {
log.push(`end ${i}`);
resolve(`r${i}`);
}, ms));
});
test('The results come back in input order', async () => {
const got = await loadAll(makeLoaders([]));
assert.deepEqual(got, ['r0', 'r1', 'r2'], `loadAll gave ${JSON.stringify(got)}`);
});
test('Every loader starts before any of them ends', async () => {
const log = [];
await loadAll(makeLoaders(log));
assert.deepEqual(log.slice(0, 3), ['start 0', 'start 1', 'start 2'], `the order was ${log.join(', ')}`);
});
package.json
{
"type": "module"
}
package.json tells Node.js that the .js files are modules; keep it in the folder.
Run the program:
node main.jsRun the checks (needs learnrun.js in the same folder):
node --testDownload learnrun.jsfunction loadName() {
const name = await Promise.resolve("Ada");
return name;
}
console.log(loadName());
What Node.js prints
SyntaxError: Unexpected reserved wordWhy, and the fix
In a module, await is a reserved word, so a plain function that uses it does not even compile, and nothing runs. Mark the function async: async function loadName() { … }. Then remember that its caller gets a promise, so it needs await loadName() too.
async function getPrice() {
return 4.5;
}
const total = getPrice() + 1;
console.log(total);
console.log(total.toFixed(2));
What Node.js prints
TypeError: total.toFixed is not a functionWhy, and the fix
getPrice() returns a promise, and + turns that promise into the text [object Promise], so total is the string "[object Promise]1", and strings have no toFixed. The first line prints it without any error. Write const total = (await getPrice()) + 1;.
const wait = (ms, fail) =>
new Promise((resolve, reject) => setTimeout(() => (fail ? reject(new Error("fast one failed")) : resolve("ok")), ms));
async function both() {
const slow = wait(50, false);
const fast = wait(10, true);
try {
return [await slow, await fast];
} catch (error) {
return "handled: " + error.message;
}
}
console.log(await both());
What Node.js prints
Error: fast one failedWhy, and the fix
fast rejects after 10 ms, while the function is still waiting for slow, so nothing handles fast yet. Node.js treats that as an unhandled rejection and stops the program; the catch never runs and “handled” is never printed. MDN warns about exactly this. Await both at once: return await Promise.all([slow, fast]); inside the try.
JavaScript in the browser: your browser’s own engine, in a sandboxed worker. Syntax errors are located with acorn 8.18.0, MIT. Licence and source
5 questions, no hints. Score 80% or more to complete the lesson.
Finish every activity above to unlock the exit ticket.