In our previous post on the generator function,
Understanding Generator Functions in JavaScript
let me break down this code step by step, so you can better understand what’s happening when we combine generator functions with Promises. Here’s the code again:
const generator = fetchData();
generator.next().value
.then(response => generator.next(response).value)
.then(data => generator.next(data));
Context
This code is part of an example that uses a generator function (fetchData
) to handle an asynchronous operation with fetch
. The function yields a Promise
(the fetch
call), and then waits for the Promise
to resolve before continuing.
Let’s look at the fetchData
function again:
function* fetchData() {
const response = yield fetch("https://jsonplaceholder.typicode.com/todos/1");
const data = yield response.json();
console.log(data);
}
This generator function does two asynchronous things:
fetch()
: It fetches data from an API and yields the resultingPromise
.response.json()
: After the firstyield
, it waits for the response object, and then yields aPromise
again to parse the JSON.
The tricky part is that the generator pauses at each yield
until you manually call .next()
to resume its execution, which is why we are chaining .next()
calls with the Promise.then()
pattern.
Now let’s break down the key lines:
const generator = fetchData(); // Start the generator, but no code inside has run yet
Here, we create an iterator (generator
) from the generator function. At this point, fetchData()
has not run its code yet; it will start when we first call generator.next()
.
Step 1: Call .next()
and Yield the First Promise
generator.next().value
- The first
generator.next()
starts the generator and executes the code up to the firstyield
. - The first
yield
returns thePromise
from thefetch()
call. .value
extracts thePromise
from the object{ value: promise, done: false }
.
Step 2: Handle the fetch
Promise with .then()
.then(response => generator.next(response).value)
- Once the
Promise
(returned byfetch()
) is resolved, the.then()
callback is executed. response
is the result of thefetch
call, and we pass it back into the generator usinggenerator.next(response)
.- This resumes the generator function, where
response
is assigned to theconst response
in the lineconst response = yield fetch(...)
. - The generator now runs until the next
yield
, which happens withyield response.json()
. This yields anotherPromise
(theresponse.json()
method that parses the response as JSON). - We access this second
Promise
with.value
again.
Step 3: Handle the JSON Parsing Promise
.then(data => generator.next(data));
- Once the second
Promise
(fromresponse.json()
) resolves, the.then()
block runs. - The parsed JSON data (
data
) is passed back into the generator withgenerator.next(data)
. - This completes the generator function, and
console.log(data)
is executed, printing the final parsed JSON data to the console.
Simplified Breakdown
- First
yield
:fetch()
is called, yielding aPromise
.
- Wait for the
Promise
to resolve.
- Second
yield
: Once thefetch
is done, pass theresponse
togenerator.next(response)
.
- Wait for
response.json()
to resolve (anotherPromise
).
- Complete: Pass the parsed JSON (
data
) togenerator.next(data)
and finish execution.
Visual Representation of Execution:
- Start generator →
fetchData()
begins. - First
next()
→ Callsfetch()
and yields the resultingPromise
. - First
.then()
→ Handles the response and passes it back into the generator. - Second
next()
→ Callsresponse.json()
and yields anotherPromise
. - Second
.then()
→ Handles the parsed JSON and passes it back into the generator. - Complete → Logs the final data.
What’s Really Happening
This pattern is a way of using generators to manage asynchronous code in a sequential way. Without generators, you’d usually see Promises
being chained in a .then()
fashion. Generators make this process cleaner and easier to read in certain cases, especially when you combine them with async/await
(which was designed to replace this manual generator-based flow).
Modern Approach with async/await
You can achieve the same functionality using async/await
, which is easier to understand:
async function fetchData() {
const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const data = await response.json();
console.log(data);
}
fetchData();
This version does the same thing but with cleaner syntax, and it’s easier to manage for most people, as it removes the need for manually calling .next()
.
Conclusion
The code you saw demonstrates how generator functions work with promises, allowing you to pause and resume execution. However, in modern JavaScript development, async/await
has become the preferred way to handle asynchronous operations because it’s much more readable and does not require manual next()
calls.