Callbacks stack up
The callback style from the last two lessons works, but real request handlers chain several slow steps: check the session, then load the user, then save a row. With callbacks, each step must nest inside the previous one, and the error check has to be repeated at every level:
getSession(id, (err, session) => { if (err) return handle(err); getUser(session.userId, (err, user) => { if (err) return handle(err); saveOrder(user, (err, order) => { if (err) return handle(err); // finally, the actual work }); }); });
Code that keeps drifting rightward like this is hard to read, hard to reorder, and easy to get error handling wrong in. JavaScript's answer is the promise: an object that stands for a value that has not arrived yet. A promise is in one of three states: pending (still waiting), fulfilled (the value arrived), or rejected (it failed with an error). Instead of handing a callback in, you get a promise back and attach what should happen next:
.then(fn)runsfnwith the value once it arrives, and itself returns a new promise, so steps chain flat instead of nesting..catch(fn)handles a rejection from ANY earlier step in the chain, so one error handler covers everything above it.
This matters daily: nearly every modern Node API returns promises, including fs.promises, database drivers, and fetch (JavaScript's built-in function for making HTTP requests). The callback material still applies, promises are built on the same event loop, but promise style is what you will read and write in current codebases.
Code exercise · javascript
Run this. loadUser returns a promise instead of taking a callback. The first .then receives Ada and starts a second load, which rejects; the single .catch at the end handles that failure, so "never printed" is skipped.
async/await: promises, readable
Chained .then calls beat nesting, but JavaScript went one step further with syntax that makes promise code read top to bottom like ordinary code. Inside a function marked async, the keyword await pauses that function until a promise settles, then hands you the fulfilled value. A rejected promise becomes a normal exception, caught with the try/catch you already know:
async function createOrder(id) { try { const session = await getSession(id); const user = await getUser(session.userId); return await saveOrder(user); } catch (err) { // one handler for all three steps } }
Two facts to hold on to:
awaitsuspends only the async function it appears in. The event loop from lesson 2-2 keeps running everything else, so the server stays non-blocking while this one handler waits.- An
asyncfunction always returns a promise itself, so callers canawaitit in turn.
This is the house style of modern Node code and of every framework's documentation, so from here the course shows both styles where it helps.
Code exercise · javascript
Your turn. Rewrite the promise chain in async/await style. Inside main: await loadUser(42) and print "loaded: " plus the name, then await loadUser(7) followed by printing "never printed", all inside one try. The catch prints "failed: " plus err.message. The output must match the .then version exactly.
Quiz
A request handler runs `const user = await db.getUser(id);` and the database takes 80 ms to answer. What is the Node server doing during those 80 ms?