Async Patterns (callbacks → promises → async/await)
Asynchronous code lets Node start slow work — files, networks, timers — and keep running instead of freezing, then handle the result later with callbacks, promises, or async/await.
Learn Async Patterns (callbacks → promises → async/await) in our free Node.js course — a beginner-friendly interactive lesson with worked examples, a…
Part of the free Node.js course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
By the end of this lesson you'll trace how a callback runs later, see why nested callbacks become "callback hell," rewrite the same logic with promises, and finally with clean async/await using try/catch.
What You'll Learn in This Lesson
1️⃣ Callbacks: "Call Me Back When You're Done"
The oldest async pattern in Node is the callback : you pass a function into a slow operation, and Node runs that function once the work finishes. Because Node is non-blocking, the rest of your script keeps running in the meantime. Read the comments and predict the print order before you check.
Callbacks work well for one step. The trouble starts when each async step depends on the previous one — the code nests sideways into a staircase known as callback hell .
2️⃣ Promises: A Value That Isn't Ready Yet
A promise is an object that stands in for a result that will arrive later. It either resolves (success) or rejects (failure). You attach .then() for success and .catch() for failure — and chaining .then() keeps sequential steps flat instead of nested.
3️⃣ async/await: Async Code That Reads Top-to-Bottom
async/await is syntax built on promises. Mark a function async , then use await to pause until a promise resolves and hand you its value — no .then() , no nesting. Errors are caught with ordinary try/catch . Remember: await pauses only its own function, never the whole program.
Notice that "main() started" prints first : calling an async function returns immediately, and the awaited work resumes later. That's the non-blocking event loop at work.
Your turn. Finish the async function below by replacing each marked spot. Follow the // TODO comments, then run it and compare with the expected output.
Mini-Challenge: Load Two Things in Parallel
No blanks this time — just a brief and an outline. Use Promise.all to await two promises at once, then combine the results into one line. Run it with node parallel.js and check your output against the example.
Common Errors (and the fix)
- "SyntaxError: await is only valid in async functions" — You used await in a normal function. Add the async keyword to the function that contains it.
- Logging a promise prints Promise { <pending> } — You forgot to await it (or chain .then() ). The value isn't ready yet; await it to unwrap the result.
- "UnhandledPromiseRejection" — A promise rejected with no handler. Add a .catch() , or wrap the await in try/catch .
- Forgetting to return inside .then() — If a .then() doesn't return its value, the next .then() receives undefined . Always return the value you want to pass along.
- Awaiting in a loop when you meant parallel — await inside a for loop runs items one at a time. For independent work, collect promises and use Promise.all .
Pro Tips
- 💡 Prefer async/await: for new code it's the clearest of the three. Reach for raw promises mainly when combining many at once with Promise.all .
- 💡 One try/catch can cover several awaits: you don't need a separate try/catch per line — wrap a logical block.
- 💡 Async returns a promise: every async function gives back a promise, so callers can await it too.
📋 Quick Reference — Async Patterns
Goal
Syntax
Run later (callback)
setTimeout(cb, ms)
Make a promise
new Promise((resolve, reject) => {})
Handle success / failure
.then(v => ...).catch(e => ...)
Await a value
const v = await p
Catch async errors
try { await p } catch (e) {}
Run in parallel
await Promise.all([a, b])
Frequently Asked Questions
🎉 Lesson Complete!
- ✅ A callback is a function Node runs later , once slow work finishes
- ✅ Deeply nested callbacks become unreadable "callback hell"
- ✅ A promise represents a value that isn't ready yet — .then() / .catch()
- ✅ async/await makes async code read top-to-bottom, with try/catch
- ✅ await pauses only its function — it never blocks the whole program
- ✅ Use Promise.all to run independent async work in parallel
- ✅ Next lesson: Error Handling — try/catch, error events, and crashing gracefully
Practice quiz
What is a callback in Node.js?
- A way to pause the whole program
- A special kind of variable
- A function passed to another function to run later when work finishes
- A built-in Node module
Answer: A function passed to another function to run later when work finishes. A callback is a function you hand to an async operation so it can call you back once the slow work completes.
In the callback example, what order do the three messages print?
- 1, 2, 3
- 1, 3, 2
- 3, 2, 1
- 2, 1, 3
Answer: 1, 2, 3. Because Node is non-blocking, the synchronous line 2 runs before the setTimeout callback (line 3).
What term describes deeply nested callbacks that grow sideways?
- Promise chaining
- Event looping
- Async sprawl
- Callback hell
Answer: Callback hell. When each async step nests inside the previous callback, the staircase shape is called callback hell.
What does a Promise represent?
- A synchronous return value
- A value that is not ready yet and will resolve or reject later
- A timer that always fires
- A network socket
Answer: A value that is not ready yet and will resolve or reject later. A Promise stands in for a future result that eventually resolves (success) or rejects (failure).
Which method handles a successful promise value?
- .then()
- .catch()
- .finally()
- .reject()
Answer: .then(). .then() runs on success; .catch() runs on failure.
What keyword lets you use await inside a function?
- defer
- yield
- async
- wait
Answer: async. Marking a function async lets you use await inside it; await is only valid in async functions.
Does await block the entire program while it waits?
- Yes, the whole process freezes
- No, it only pauses its own async function
- Only on Windows
- Only for network calls
Answer: No, it only pauses its own async function. await pauses just the async function it is in; the event loop keeps running everything else.
What does every async function return?
- undefined always
- A callback
- A Buffer
- A promise
Answer: A promise. An async function always returns a promise, so callers can await it too.
When should you use Promise.all?
- To run promises strictly one after another
- To run independent async operations in parallel and await them together
- To cancel a promise
- To convert a callback to a promise
Answer: To run independent async operations in parallel and await them together. Promise.all starts independent operations together, so total time is the slowest one, not the sum.
How do you catch a rejected promise when using await?
- Add a second argument to await
- Use .then() only
- Wrap the await in try/catch
- It cannot be caught
Answer: Wrap the await in try/catch. try/catch around an await handles a rejected promise just like a thrown error.
Continue this course
- Previous: Routing by Hand
- Next: Error Handling