Chapter 6: Chapter 6. Functions for the future: generators and promises
Introduces generators and promises as ES6 features, explaining their basic roles before the chapter explores how to use them.
After this chapter you will be able to
- Generator execution and values. Explain how a generator differs from a standard function: it can produce multiple values on request and suspend execution between requests.
- Promises as future values. Describe a promise as a built-in object that stands in for a value not yet available, and explain its usefulness for asynchronous code with multiple steps.
- Blocking server requests. Explain why straightforward sequential server requests can make the application unresponsive, even though their code is easy to follow and handle errors in.
- Nonblocking code tradeoff. Distinguish the benefit of callbacks from their readability cost, and describe why the section introduces generators and promises together.
- On-demand generator values. Explain that a generator produces a sequence one value per request, suspending after a value and resuming where it left off when another is requested.
- Generator call result. Explain that calling a generator creates an iterator object rather than immediately executing the generator body, so the absence of a return statement does not make the for-of expression simply `undefined`.
- Generator iterator execution. Trace what happens when a generator is called and its iterator’s next method is called repeatedly, including yields, suspension, and completion.
- Iterator loop consumption. Explain how a while loop consumes generator results using next and done, and how a for-of loop performs that iteration behind the scenes.
- Yield star delegation. Explain how yield* delegates from one generator to another and when the original generator resumes.
- Generator-Based Unique ID Generation. Explain how a generator supplies unique IDs on demand while keeping its counter local rather than exposing a global variable.
- Generator-Based DOM Traversal. Describe how generators turn a recursive-style DOM traversal into a sequence of nodes that a separate for-of loop can consume.
- Values sent to generators. Trace how a generator yields a value and then receives a new value through the iterator’s next() method. Distinguish that later input from an initial argument supplied when calling the generator.
- Generator States and Transitions. Describe the four generator states and what moves a generator between them: creation, calls to the iterator’s next method, yield, and return or the end of the code.
- Preserved Generator Execution Context. Explain how the iterator retains a generator’s execution context after it leaves the stack, and trace how next reactivates that context so execution continues after a yield rather than starting a fresh call.
- Promise as future result. Explain that a promise represents the eventual result of an asynchronous computation, which may be a value or an error.
- Promise construction and settlement. Describe creating a promise with the Promise constructor and an executor that receives resolve and reject functions.
- Then callbacks for outcomes. Trace how then selects a success or failure callback, including why the example invokes the success callback.
- Callback error handling difficulty. Explain why a try-catch around the code that starts an asynchronous task does not reliably handle errors from its callback, and describe the error-reporting convention given as an example.
- Nested dependent callback steps. Describe how dependent asynchronous tasks lead to nested callbacks and why that structure is difficult to manage.
- Parallel callback coordination. Explain why independent asynchronous tasks can run in parallel and what extra coordination callbacks require before using all their results.
- Promise values and states. Explain how a promise stands in for the future result of an asynchronous task and describe its possible states.
- Asynchronous success callback order. Trace why the example logs the end of the current code before either promise’s success callback, even though one promise resolves during construction.
- Explicit and implicit rejection. Distinguish calling the executor’s reject method from an unhandled exception that implicitly rejects a promise. Explain why the undeclared-variable example rejects its promise.
- Promise rejection callbacks. Describe how the second callback to then or a catch chained after then handles a rejected promise. Explain that both explicit rejections and implicit exceptions reach the rejection callback.
- Promise outcomes for JSON requests. Trace how getJSON uses XMLHttpRequest events to settle its promise: reject on communication errors, non-200 responses, or JSON parsing errors, and resolve with successfully parsed objects.
- Sequential promise chaining. Explain that then registers a success callback and returns a new promise, allowing dependent asynchronous steps to be chained. Trace the example from fetching ninjas to fetching the first ninja’s missions, then the first mission’s details, and finally logging those details.
- Chain wide error handling. Explain when a single catch is useful in a promise chain and what happens if an earlier promise fails or no error occurs.
- Waiting for all promises. Explain how Promise.all coordinates independent asynchronous tasks and what causes its returned promise to resolve or reject.
- Racing promise outcomes. Describe when Promise.race is useful and how the first promise to resolve or reject determines its returned promise.
- Yielding Promises Without Blocking. Explain how code in a generator yields a promise for an asynchronous task, suspends while the task runs, and resumes when the promise settles.
- Promise Results Returning To Generator. Trace how the example’s async helper handles a yielded promise: a successful result is sent into the generator with the iterator’s next method, while a rejection is sent in with its throw method. Describe how the resumed generator can use one fetched result to start the next dependent request.
- Async and await roles. Explain how async marks a function that relies on asynchronous values and how await requests a result without blocking, replacing boilerplate previously needed to coordinate generators and promises.
What it builds on
- Parsing the HTML and building the DOM (chapter 2) · The DOM as a tree of HTML nodes.
- Tracking code execution with execution contexts (chapter 5) · Execution contexts and the execution context stack.
- Defining functions (chapter 3) · Assumes familiarity with passing a function as an argument.
- Callback functions (chapter 3) · Assumes familiarity with callback functions.
Section by section
Introduction
Introduces generators and promises as ES6 features, explaining their basic roles before the chapter explores how to use them.
- Generator execution and values: Explain how a generator differs from a standard function: it can produce multiple values on request and suspend execution between requests.
Why it matters: This distinction explains why generators can be useful for simplifying loops and asynchronous code. - Promises as future values: Describe a promise as a built-in object that stands in for a value not yet available, and explain its usefulness for asynchronous code with multiple steps.
Why it matters: Thinking of a promise as a future value gives you a starting point for reasoning about asynchronous work.
Key terms: generators, suspend, resume, promises, asynchronous code, placeholder
6.1 Making our async code elegant with generators and promises
The section compares blocking server requests, nonblocking callbacks, and a more elegant approach combining generators and promises.
- Blocking server requests: Explain why straightforward sequential server requests can make the application unresponsive, even though their code is easy to follow and handle errors in.
Why it matters: This is the problem the section's asynchronous alternatives are meant to avoid. - Nonblocking code tradeoff: Distinguish the benefit of callbacks from their readability cost, and describe why the section introduces generators and promises together.
Why it matters: It explains why avoiding a frozen UI is not the only concern when writing asynchronous code.
Key terms: single-threaded execution model, blocked our UI, callbacks, generators, promises, nonblocking, boilerplate error-handling code
6.1 Do you know?
This section poses diagnostic questions about generators, promises, and Promise.race without explaining their answers.
6.2 Working with generator functions
Introduces generators as functions that produce values on request, shows their basic syntax and use in a for-of loop, and explains what calling one returns.
- On-demand generator values: Explain that a generator produces a sequence one value per request, suspending after a value and resuming where it left off when another is requested.
Why it matters: This pause-and-resume behavior is what makes a generator different from an ordinary function. - Generator definition and consumption: Describe how `function*` defines a generator, how `yield` produces individual values, and how the example consumes its weapon sequence with a for-of loop.
Why it matters: The syntax and example connect the generator’s on-demand behavior to code you can recognize. - Generator call result: Explain that calling a generator creates an iterator object rather than immediately executing the generator body, so the absence of a return statement does not make the for-of expression simply `undefined`.
Why it matters: Distinguishing a generator call from an ordinary call explains why the loop can use its result despite there being no return statement.
Key terms: generator, yield, suspended, generator function, for-of loop, iterator
6.2.1 Controlling the generator through the iterator object
This section explains how an iterator controls a generator, how loops consume its values, and how yield* delegates to another generator.
- Generator iterator execution: Trace what happens when a generator is called and its iterator’s next method is called repeatedly, including yields, suspension, and completion.
Why it matters: It explains why generator values appear on request rather than all at once. - Iterator loop consumption: Explain how a while loop consumes generator results using next and done, and how a for-of loop performs that iteration behind the scenes.
Why it matters: It connects explicit iterator control to the simpler for-of syntax. - Yield star delegation: Explain how yield* delegates from one generator to another and when the original generator resumes.
Why it matters: It shows how one generator can include another generator’s values without changing how callers iterate.
Key terms: generator, iterator object, next, yield, value, done, while loop, for-of loop, syntactic sugar, yield*
6.2.2 Using generators
Shows two practical uses of generators: producing unique IDs on demand and traversing a DOM tree as a sequence of nodes.
- Generator-Based Unique ID Generation: Explain how a generator supplies unique IDs on demand while keeping its counter local rather than exposing a global variable.
Why it matters: It shows how to request IDs without leaving a counter open to accidental changes elsewhere in the code. - Generator-Based DOM Traversal: Describe how generators turn a recursive-style DOM traversal into a sequence of nodes that a separate for-of loop can consume.
Why it matters: It lets traversal produce nodes while a separate loop decides what to do with them.
Key terms: generator, id, yield, iterator object, next(), DOM traversal, generator function, for-of loop, callbacks
6.2.3 Communicating with a generator
This section explains how to send values or exceptions into a generator after it has yielded, enabling two-way communication.
- Values sent to generators: Trace how a generator yields a value and then receives a new value through the iterator’s next() method. Distinguish that later input from an initial argument supplied when calling the generator.
Why it matters: It shows how a generator can exchange information with its caller rather than only produce values. - Exceptions sent to generators: Describe how an iterator’s throw method sends an exception back to a generator and how the section’s example prepares the generator to handle it.
Why it matters: Exceptions provide another way for a caller to communicate with a suspended generator.
Key terms: yield expression, iterator, next method, generator function arguments, two-way communication, throw method, exception, try-catch block
6.2.4 Exploring generators under the hood
This section explains generator state changes and how an iterator preserves and reactivates a generator’s execution context between yields.
- Generator States and Transitions: Describe the four generator states and what moves a generator between them: creation, calls to the iterator’s next method, yield, and return or the end of the code.
Why it matters: These states explain why a generator can produce a value now and continue later. - Preserved Generator Execution Context: Explain how the iterator retains a generator’s execution context after it leaves the stack, and trace how next reactivates that context so execution continues after a yield rather than starting a fresh call.
Why it matters: Keeping the same context alive is what lets a generator pick up where it left off.
Key terms: Suspended start, Executing, Suspended yield, Completed, yield, next, execution context, execution context stack, iterator, ninjaIterator
6.3 Working with promises
Introduces promises as placeholders for asynchronous results and shows how to create one and handle its success or failure.
- Promise as future result: Explain that a promise represents the eventual result of an asynchronous computation, which may be a value or an error.
Why it matters: This explains why promises are useful when an asynchronous result is not available immediately. - Promise construction and settlement: Describe creating a promise with the Promise constructor and an executor that receives resolve and reject functions.
Why it matters: It connects the promise’s possible outcomes to the functions supplied when the promise is created. - Then callbacks for outcomes: Trace how then selects a success or failure callback, including why the example invokes the success callback.
Why it matters: It shows how code responds differently to an asynchronous success and a problem.
Key terms: promise, asynchronous computation, Promise constructor, executor function, resolve, reject, then, success callback, failure callback
6.3.1 Understanding the problems with simple callbacks
This section explains why simple callbacks make asynchronous work difficult and why JavaScript introduced promises as a standard approach.
- Asynchronous callbacks and responsiveness: Explain why an application uses a callback for a long-running task such as fetching a JSON file.
Why it matters: This is the reason the section uses callbacks in the first place, before examining their drawbacks. - Callback error handling difficulty: Explain why a try-catch around the code that starts an asynchronous task does not reliably handle errors from its callback, and describe the error-reporting convention given as an example.
Why it matters: Error handling is the first concrete limitation of simple callbacks that motivates a different approach. - Nested dependent callback steps: Describe how dependent asynchronous tasks lead to nested callbacks and why that structure is difficult to manage.
Why it matters: The difficulty of expressing dependent steps is a major reason to move beyond simple callbacks. - Parallel callback coordination: Explain why independent asynchronous tasks can run in parallel and what extra coordination callbacks require before using all their results.
Why it matters: Parallel work can save time, but simple callbacks make waiting for all the results cumbersome. - Need for a standard approach: Justify the introduction of promises using the callback difficulties and limits of library-specific solutions described here.
Why it matters: It connects the callback limitations to the reason for introducing promises without assuming how promises work.
Key terms: asynchronous code, long-running task, callback, try-catch, event loop, err, data, nested callbacks, pyramid of doom, interdependent
6.3.2 Diving into promises
This section explains promise states and uses immediate and delayed promises to show when their success callbacks run.
- Promise values and states: Explain how a promise stands in for the future result of an asynchronous task and describe its possible states.
Why it matters: The state tells you whether the future result is still unknown, available, or unavailable. - Promise construction and callbacks: Describe what happens when the example constructs its delayed and immediate promises and registers success callbacks with then.
Why it matters: The contrast separates when a promise gets its result from when its registered callback runs. - Asynchronous success callback order: Trace why the example logs the end of the current code before either promise’s success callback, even though one promise resolves during construction.
Why it matters: This ordering prevents an already-resolved promise from making a then callback run in the middle of the current code.
Key terms: promise, pending, unresolved promise, fulfilled, rejected, resolved, Promise constructor, executor function, resolve function, then method
6.3.3 Rejecting promises
This section explains how promises are rejected explicitly or by unhandled exceptions, and how either rejection reaches an error callback.
- Explicit and implicit rejection: Distinguish calling the executor’s reject method from an unhandled exception that implicitly rejects a promise. Explain why the undeclared-variable example rejects its promise.
Why it matters: A promise can fail even when your code never calls reject. - Promise rejection callbacks: Describe how the second callback to then or a catch chained after then handles a rejected promise. Explain that both explicit rejections and implicit exceptions reach the rejection callback.
Why it matters: One rejection callback can handle both deliberate failures and unexpected exceptions.
Key terms: reject method, executor function, implicit rejection, exception, then method, second, error, callback, catch method, rejection callback
6.3.4 Creating our first real-world promise
This section uses a JSON-fetching function to show how a promise handles asynchronous success and several kinds of failure through a single interface.
- Promise outcomes for JSON requests: Trace how getJSON uses XMLHttpRequest events to settle its promise: reject on communication errors, non-200 responses, or JSON parsing errors, and resolve with successfully parsed objects.
Why it matters: It shows how one promise represents the outcome of a request even though several things can go wrong. - Unified success and failure callbacks: Explain why code using getJSON can register one callback for properly received data and one for any error, without handling each error source separately.
Why it matters: The caller can focus on what to do with data or an error rather than managing every failure case itself.
Key terms: getJSON, XMLHttpRequest, onload, onerror, status 200, JSON.parse, success and failure callbacks, error sources
6.3.5 Chaining promises
This section explains how chaining promises expresses dependent asynchronous steps without deeply nested callbacks and how one catch can handle failures across the chain.
- Sequential promise chaining: Explain that then registers a success callback and returns a new promise, allowing dependent asynchronous steps to be chained. Trace the example from fetching ninjas to fetching the first ninja’s missions, then the first mission’s details, and finally logging those details.
Why it matters: Chaining makes the order of dependent asynchronous work easier to see and change. - Chain wide error handling: Explain when a single catch is useful in a promise chain and what happens if an earlier promise fails or no error occurs.
Why it matters: One catch can handle failure across the sequence when individual steps do not need separate error handling.
Key terms: then, promise chaining, pyramid of doom, getJSON, catch, error callback, chained promises
6.3.6 Waiting for a number of promises
This section explains how Promise.all waits for multiple independent asynchronous tasks and how Promise.race responds to the first one that settles.
- Waiting for all promises: Explain how Promise.all coordinates independent asynchronous tasks and what causes its returned promise to resolve or reject.
Why it matters: It lets you wait for several independent tasks without manually tracking which has finished. - Racing promise outcomes: Describe when Promise.race is useful and how the first promise to resolve or reject determines its returned promise.
Why it matters: It handles cases where the first outcome matters, without manually tracking every task.
Key terms: Promise.all, array of promises, succeed values, Promise.race, resolves, rejects
6.4 Combining generators and promises
The section shows how a generator and promises can coordinate dependent asynchronous requests without blocking, while keeping the business logic readable.
- Yielding Promises Without Blocking: Explain how code in a generator yields a promise for an asynchronous task, suspends while the task runs, and resumes when the promise settles.
Why it matters: This is how the example waits for remote data without freezing the UI. - Promise Results Returning To Generator: Trace how the example’s async helper handles a yielded promise: a successful result is sent into the generator with the iterator’s next method, while a rejection is sent in with its throw method. Describe how the resumed generator can use one fetched result to start the next dependent request.
Why it matters: This explains how dependent requests can be written in sequence even though their results arrive asynchronously. - Readable Nonblocking Business Logic: Justify combining generators and promises rather than running the dependent requests synchronously: the resulting business logic retains straightforward control flow and exception handling without blocking while requests are pending.
Why it matters: It connects the helper’s mechanics to the practical reason for using them.
Key terms: generator, yield, promise, iterator, async function, handle function, next, throw, getJSON, control-flow
6.4.1 Looking forward—the async function
This section introduces async and await as planned language support that makes asynchronous code easier to write without the generator-and-promise boilerplate.
- Async and await roles: Explain how async marks a function that relies on asynchronous values and how await requests a result without blocking, replacing boilerplate previously needed to coordinate generators and promises.
Why it matters: It lets you describe why async code can be written without manually coordinating promises and a generator. - Async function availability: State the section’s distinction between native browser support and using a transpiler to write async functions at the time described.
Why it matters: It distinguishes the proposed language feature from the way developers could try it at the time.
Key terms: async, await, generators, promises, boilerplate code, async functions, transpilers, Babel, Traceur
6.5 Summary
6.6 Exercises
Where this is used later
- Do you know? (chapter 13) · The event-loop discussion expands on the execution timing mentioned here.
Concepts explained in this chapter
- generatorA JavaScript function that can yield values, suspend execution, and later resume with its local state preserved.
- promiseA JavaScript object representing the eventual completion or failure of an asynchronous operation and its resulting value.
- nextAn iterator method that advances iteration and returns the next result; for a generator, it can also pass a value into the suspended computation.
- callbackA function supplied to other code to be invoked later when a condition or event occurs.
- yieldAn expression in a JavaScript generator that yields a value and evaluates to a value supplied when execution resumes.
- iteratorAn object that produces a sequence of values one at a time through an iteration protocol, typically using a next method.
- execution contextAn internal JavaScript runtime record that tracks the code being evaluated and the environment used to resolve identifiers.
- asynchronous programmingProgram code structured to handle operations that may complete after control has returned to the caller.
- async/awaitA language keyword that pauses an async function until a promise settles and then evaluates to the promise's result or throws its rejection.
- async functionA JavaScript function marked as asynchronous that can use await and returns a promise.
- for...ofA JavaScript loop that iterates over iterable values using the iterator protocol.
- error handlingThe practice of detecting and responding to errors that occur during program execution.
- finite-state machineA model of a system as a set of states and transitions between those states.
- Promise.allA JavaScript Promise combinator that returns a promise fulfilled when all input promises fulfill, or rejected if any input promise rejects.
- then methodA Promise method that registers fulfillment and rejection handlers and returns a new promise for chaining.
- fulfilled stateThe promise state indicating successful completion with a value.
- pending stateThe promise state in which the final outcome has not yet been determined.
- rejected stateThe promise state indicating unsuccessful completion with a rejection reason.
- executor functionThe function passed to a Promise constructor that runs immediately and receives resolve and reject callbacks.
- syntactic sugarLanguage syntax that makes an operation easier to read or write without adding new underlying capabilities.
- execution context stackA stack-like runtime structure that records active execution contexts for nested calls and returns.
- identifierA name used to identify an entity in a program.
- lexical environmentAn internal JavaScript structure that holds identifier bindings and links to an outer environment for name resolution.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
