Read this introduction before the session. It explains the concepts we'll practice during the session.
The Problem: Callback Hell
Last week you wrote code like this:
validateOrder(order,(err,validation)=>{if (err) returnconsole.error(err);calculateTotal(order,(err,total)=>{if (err) returnconsole.error(err);checkStock(order,(err,stock)=>{if (err) returnconsole.error(err);processPayment(order,total,(err,receipt)=>{if (err) returnconsole.error(err); // Finally done... but look at this pyramid!});});});});
This is called callback hell - deeply nested callbacks that are hard to read and maintain. Each new step adds another level of indentation. Error handling is repetitive. The code grows sideways instead of downward.
Promises solve this.
What is a Promise?
A Promise is an object representing an operation that hasn't completed yet. Think of it like an order ticket at a cafe:
You place an order and receive a ticket (the Promise)
The ticket promises you'll get coffee eventually
Two outcomes: you get your coffee (resolved) or they're out (rejected)
📚 Glossary: Promise An object representing the eventual completion (or failure) of an asynchronous operation. A Promise is always in one of three states: pending, fulfilled (resolved), or rejected.
Consuming Promises: .then() and .catch()
When you have a Promise, you can attach callbacks using .then() and .catch():
.then() runs when the Promise resolves (succeeds)
.catch() runs when the Promise rejects (fails)
📚 Glossary: resolve / reject A Promise "resolves" when the operation succeeds, passing the result to .then(). It "rejects" when the operation fails, passing the error to .catch().
Chaining Promises
The magic of Promises: .then() returns a new Promise, so you can chain them:
No more nesting! The code flows downward, and one .catch() handles all errors.
📚 Glossary: then / catch.then(callback) schedules a callback to run when a Promise resolves. .catch(callback) schedules a callback to run when a Promise rejects. Both return new Promises, enabling chaining.
Creating Promises
You can create your own Promises using new Promise():
The Promise constructor takes a function with two parameters:
resolve - call this when the operation succeeds
reject - call this when the operation fails
async/await: Even Cleaner Syntax
async/await is syntactic sugar over Promises - it makes async code look synchronous:
async before a function makes it return a Promise
await pauses execution until a Promise resolves
The code reads top-to-bottom, like synchronous code
📚 Glossary: async / awaitasync declares a function that returns a Promise. await pauses execution until a Promise settles, then returns its resolved value. Only works inside async functions.
Error Handling with try/catch
With async/await, you handle errors using try/catch:
This is the same pattern as synchronous error handling - familiar and readable.
Promise.all: Parallel Operations
Sometimes you want to run multiple operations at once:
Promise.all() takes an array of Promises and returns a Promise that resolves when ALL of them resolve.
📚 Glossary: Promise.all Takes an array of Promises and returns a single Promise that resolves when all input Promises resolve (with an array of results), or rejects when any input Promise rejects.
Base URL: https://tea-api-787553294298.europe-west1.run.app/api/v1
Endpoints:
GET /teas - List all teas
GET /teas/:id - Get single tea
GET /inventory - Get stock levels
POST /auth/login - Get auth token
GET /orders - List orders (requires auth)
POST /orders - Create order (requires auth)