[[iteration-and-flow-control]] == Iteration and Flow Control Having ((("flow control", see="promises")))covered the essential aspects of ES6 in <>, and symbols in <>, we're now in great shape to understand promises, iterators, and generators. Promises offer a different way of attacking asynchronous code flows. Iterators dictate how an object is iterated, producing the sequence of values that gets iterated over. Generators can be used to write code that looks sequential but works asynchronously, in the background, as we'll learn toward the end of the chapter. To kick off the chapter, we'll start by discussing promises. Promises have existed in user-land for a long time, but they're a native part of the language starting in ES6. === Promises Promises ((("promises", id="prom4")))((("promises", "defined")))can be vaguely defined as "a proxy for a value that will eventually become available." While we can write synchronous code inside promises, promise-based code flows in a strictly asynchronous manner. Promises can make asynchronous flows easier to reason about--once you've mastered promises, that is. ==== Getting Started with Promises As an ((("promises", "basics of", id="p4bo")))((("fetch", id="fetch4")))((("promises", "in fetch", id="p4if")))example, let's take a look at the new `fetch` API for the browser. This API is a simplification of `XMLHttpRequest`. It aims to be super simple to use for the most basic use cases: making a `GET` request against an HTTP resource. It provides an extensive API that caters to advanced use cases, but that's not our focus for now. In its most basic incarnation, you can make a `GET /items` HTTP request using a piece of code like the following. [source,javascript] ---- fetch('/items') ---- The `fetch('/items')` statement doesn't seem all that exciting. It makes a "fire and forget" `GET` request against `/items`, meaning you ignore the response and whether the request succeeded. The `fetch` method returns a `Promise`. You can chain a callback using the `.then` method on that promise, and that callback will be executed once the `/items` resource finishes loading, receiving a `response` object parameter. [source,javascript] ---- fetch('/items').then(response => { // do something }) ---- The following bit of code displays the promise-based API with which `fetch` is actually implemented in browsers. Calls to `fetch` return a `Promise` object. Much like with events, you can bind as many reactions as you'd like, using the `.then` and `.catch` methods. [source,javascript] ---- const p = fetch('/items') p.then(res => { // handle response }) p.catch(err => { // handle error }) ---- Reactions passed to `.then` can be used to handle the fulfillment of a promise, which is accompanied by a fulfillment value; and reactions passed to `.catch` are executed with a rejection `reason` that can be used when handling rejections. You can also register a reaction to rejections in the second argument passed to `.then`. The previous piece of code could also be expressed as the following. [source,javascript] ---- const p = fetch('/items') p.then( res => { // handle response }, err => { // handle error } ) ---- Another alternative is to omit the fulfillment reaction in `.then(fulfillment, rejection)`, this being similar to the omission of a rejection reaction when calling `.then`. Using `.then(null, rejection)` is equivalent to `.catch(rejection)`, as shown in the following snippet of code. [source,javascript] ---- const p = fetch('/items') p.then(res => { // handle response }) p.then(null, err => { // handle error }) ---- .Promises as an Alternative to Callbacks and Events **** Traditionally ((("promises", "as alternative to callbacks and events", id="p4aatcae")))((("callbacks")))JavaScript relied on callbacks instead of promises and chaining. If the `fetch` function asked for a callback, you'd have to add one that would then get executed whenever the +fetch+ operation ends. Typical asynchronous code flow conventions in Node.js established a best practice of reserving the first parameter in the callback for errors--that may or may not occur--during the fetching process. The rest of the parameters can be used to read the results of the asynchronous operation. Most commonly, a single data parameter is used. The next bit of code shows how `fetch` would look if it had a callback-based API. [source,javascript] ---- fetch('/items', (err, res) => { if (err) { // handle error } else { // handle response } }) ---- The callback wouldn't be invoked until the `/items` resource has been retrieved, or an error arises from the `fetch` operation. Execution remains asynchronous and nonblocking. Note that in this model you could only specify a single callback. That callback would be responsible for all functionality derived from the response, and it'd be up to the consumer to come up with a mechanism to compose different aspects of handling the response into that single callback. Besides traditional ((("events", id="ev4")))callbacks, another API design choice might have been to use an event-driven model. In this case the object returned by `fetch` would be able to register callbacks for different kinds of events, binding as many event handlers as needed for any events--just like when you attach event listeners to the browser DOM. Typically there's an `error` event that's raised when things go awry, and other events that are raised when something notable happens. In the following piece of code, we show how `fetch` would look if it had an event-based API. [source,javascript] ---- fetch('/items') .on('error', err => { // handle error }) .on('data', res => { // handle response }) ---- Binding several listeners for each type of event would eliminate the concern we had earlier about having to centralize response handling in a single callback. Events, however, make it hard to chain callbacks and have them fire when another asynchronous task is fulfilled, and that's where promises come in. Moreover, events are better suited to handle streams of values, making them somewhat inappropriate in this particular use case. <> discusses proper asynchronous code flow design in more detail, expanding on which kinds of constructs are better suited for which kinds of code ((("events", startref="ev4")))((("promises", "as alternative to callbacks and events", startref="p4aatcae")))flows. **** When it comes to ((("promises", "chaining", id="p4c")))((("chaining", id="ch4")))promises, chaining is a major source of confusion. In an event-based API, chaining is made possible by having the `.on` method attach the event listener and then returning the event emitter itself. Promises are different. The `.then` and `.catch` methods return a new promise every time. That's important because chaining can have wildly different results depending on where you ((("fetch", startref="fetch4")))((("promises", "in fetch", startref="p4if")))append a `.then` or a `.catch` call. .Visualizing Promise Chains: A Major Source of Confusion **** The `.then` and `.catch` methods return a new promise every time, creating a tree-like data structure. If you had a `p1` promise and a `p2` promise returned by `p1.then`, the `p1` and `p2` promises would be nodes connected by the `p1.then` reaction handler. Reactions create new promises that are attached to the tree as children of the promise they're reacting to. When we chain promises, we need to understand that `p1.then(r1).then(r2)` creates two new `p2` and `p3` promises. The second reaction, `r2`, is going to fire if `p2` fulfills, while the `r1` reaction will fire when `p1` is fulfilled. When we have a statement such as `p1.then(r1); p1.then(r2)`, in contrast, both `r1` and `r2` will fire if `p1` is fulfilled. A discrepancy occurs when `p1` fulfills but `p2` doesn't. Figuring out the tree-like nature of promises is the key to unlocking a deep understanding of how promises behave. To this end, I've created an online tool called https://mjavascript.com/out/promisees[Promisees] you can use to play around with promise chains while visualizing the tree structure they leave behind, as shown in <>. [[Fig4-1]] .Promisees lets you write a piece of code and visualize how the underlying graph evolves as promises are settled in fulfillment or rejection image::images/pmjs_0401.png["Promisees lets you write a piece of code and visualize how the underlying graph evolves as promises are settled in fulfillment or rejection."] **** A promise is created by passing the `Promise` constructor a resolver that decides how and when the promise is settled, by calling either a `resolve` method that will settle the promise in fulfillment or a `reject` method that'd settle the promise as a rejection. Until the promise is settled by calling either function, it'll be in a pending state and any reactions attached to it won't be executed. The following snippet of code creates a promise from scratch where we'll wait for a second before randomly settling the promise with a fulfillment or rejection result. [source,javascript] ---- new Promise(function (resolve, reject) { setTimeout(function () { if (Math.random() > 0.5) { resolve('random success') } else { reject(new Error('random failure')) } }, 1000) }) ---- Promises can also be created using `Promise.resolve` and `Promise.reject`. These methods create promises that will immediately settle with a fulfillment value and a rejection reason, respectively. [source,javascript] ---- Promise .resolve({ result: 123 }) .then(data => console.log(data.result)) // <- 123 ---- When a `p` promise is fulfilled, reactions registered with `p.then` are executed. When a `p` promise is rejected, reactions registered with `p.catch` are executed. Those reactions can, in turn, result in three different situations depending on whether they return a value, a `Promise`, a thenable, or `throw` an error. _Thenables_ are objects considered promise-like that can be cast into a `Promise` using `Promise.resolve` as observed in <>. A reaction may return a value, which would cause the promise returned by `.then` to become fulfilled with that value. In this sense, promises can be chained to transform the fulfillment value of the previous promise over and over, as shown in the following snippet of code. [source,javascript] ---- Promise .resolve(2) .then(x => x * 7) .then(x => x - 3) .then(x => console.log(x)) // <- 11 ---- A reaction may return a promise. In contrast with the previous piece of code, the promise returned by the first `.then` call in the following snippet will be blocked until the one returned by its reaction is fulfilled, which will take two seconds to settle because of the `setTimeout` call. [source,javascript] ---- Promise .resolve(2) .then(x => new Promise(function (resolve) { setTimeout(() => resolve(x * 1000), x * 1000) })) .then(x => console.log(x)) // <- 2000 ---- A reaction may also `throw` an error, which would cause the promise returned by `.then` to become rejected and thus follow the `.catch` branch, using said error as the rejection reason. The following example shows how we attach a fulfillment reaction to the `fetch` operation. Once the `fetch` is fulfilled the reaction will throw an error and cause the rejection reaction attached to the promise returned by `.then` to be executed. [source,javascript] ---- const p = fetch('/items') .then(res => { throw new Error('unexpectedly') }) .catch(err => console.error(err)) ---- Let's take a step back and pace ourselves, walking over more examples in each particular use ((("promises", "basics of", startref="p4bo")))case. ==== Promise Continuation and Chaining In the previous section we've established that you can chain any number of `.then` calls, each returning its own new promise, but how exactly does this work? What is a good mental model of promises, and what happens when an error is raised? When an ((("promises", "error capturing", id="p4eh")))error happens in a promise resolver, you can catch that error using `p.catch` as shown next. [source,javascript] ---- new Promise((resolve, reject) => reject(new Error('oops'))) .catch(err => console.error(err)) ---- A promise will settle as a rejection when the resolver calls `reject`, but also if an exception is thrown inside the resolver as well, as demonstrated by the next snippet. [source,javascript] ---- new Promise((resolve, reject) => { throw new Error('oops') }) .catch(err => console.error(err)) ---- Errors that occur while executing a fulfillment or rejection reaction behave in the same way: they result in a promise being rejected, the one returned by the `.then` or `.catch` call that was passed the reaction where the error originated. It's easier to explain this with code, such as the following piece. [role="pagebreak-before"] [source,javascript] ---- Promise .resolve(2) .then(x => { throw new Error('failed') }) .catch(err => console.error(err)) ---- It might be easier to decompose that series of chained method calls into variables, as shown next. The following piece of code might help you visualize the fact that, if you attached the `.catch` reaction to `p1`, you wouldn't be able to catch the error originated in the `.then` reaction. While `p1` is fulfilled, ++p2++—a different promise than `p1`, resulting from calling ++p1.then++—is rejected due to the error being thrown. That error could be caught, instead, if we attached the rejection reaction to `p2`. [source,javascript] ---- const p1 = Promise.resolve(2) const p2 = p1.then(x => { throw new Error('failed') }) const p3 = p2.catch(err => console.error(err)) ---- Here is another situation where it might help you to think of promises as a tree-like data structure. In <> it becomes clear that, given the error originates in the `p2` node, we couldn't notice it by attaching a rejection reaction to `p1`. [[Fig4_2]] .Understanding the tree structure of promises reveals that rejection reactions can only catch errors that arise in a given branch of promise-based code. image::images/pmjs_0402.png["The p3 rejection handler in this example won't be able to catch the failure in p2's reaction, since it reacts to p1 instead of p2."] In order for the reaction to handle the rejection in `p2`, we'd have to attach the reaction to `p2` instead, as shown in <>. [[Fig4_3]] .By attaching a rejection handler on the branch where an error is produced, we're able to handle the rejection. image::images/pmjs_0403.png["In this example, p3 reacts to p2. This enables p3 to handle the rejection that arises in p2."] We've established that the promise you attach your reactions onto is important, as it determines what errors it can capture and what errors it cannot. It's also worth noting that as long as an error remains uncaught in a promise chain, a rejection handler will be able to capture it. In the following example we've introduced an intermediary `.then` call in between `p2`, where the error originated, and `p4`, where we attach the rejection reaction. When `p2` settles with a rejection, `p3` becomes settled with a rejection, as it depends on `p2` directly. When `p3` settles with a rejection, the rejection handler in `p4` fires. [source,javascript] ---- const p1 = Promise.resolve(2) const p2 = p1.then(x => { throw new Error('failed') }) const p3 = p2.then(x => x * 2) const p4 = p3.catch(err => console.error(err)) ---- Typically, promises like `p4` fulfill because the rejection handler in `.catch` doesn't raise any errors. That means a fulfillment handler attached with `p4.then` would be executed afterwards. The following example shows how you could print a statement to the browser console by creating a `p4` fulfillment handler that depends on `p3` to settle successfully with fulfillment. [source,javascript] ---- const p1 = Promise.resolve(2) const p2 = p1.then(x => { throw new Error('failed') }) const p3 = p2.catch(err => console.error(err)) const p4 = p3.then(() => console.log('crisis averted')) ---- Similarly, if an error occurred in the `p3` rejection handler, we could capture that one as well using `.catch`. The next piece of code shows how an exception being thrown in `p3` could be captured using `p3.catch` just like with any other errors arising in previous examples. [source,javascript] ---- const p1 = Promise.resolve(2) const p2 = p1.then(x => { throw new Error('failed') }) const p3 = p2.catch(err => { throw new Error('oops') }) const p4 = p3.catch(err => console.error(err)) ---- The following example prints `err.message` once instead of twice. That's because no errors happened in the first `.catch`, so the rejection branch for that promise wasn't executed. [source,javascript] ---- fetch('/items') .then(res => res.a.prop.that.does.not.exist) .catch(err => console.error(err.message)) .catch(err => console.error(err.message)) // <- 'Cannot read property "prop" of undefined' ---- In contrast, the next snippet will print `err.message` twice. It works by saving a reference to the promise returned by `.then`, and then tacking two `.catch` reactions onto it. The second `.catch` in the previous example was capturing errors produced in the promise returned from the first `.catch`, while in this case both rejection handlers branch off of `p`. [source,javascript] ---- const p = fetch('/items').then(res => res.a.prop.that.does.not.exist ) p.catch(err => console.error(err.message)) p.catch(err => console.error(err.message)) // <- 'Cannot read property "prop" of undefined' // <- 'Cannot read property "prop" of undefined' ---- We should ((("promises", "error catching", startref="p4eh")))observe, then, that promises can be chained arbitrarily. As we just saw, you can save a reference to any point in the promise chain and then append more promises on top of it. This is one of the fundamental points to understanding promises. Let's use the following snippet as a crutch to enumerate the sequence of events that arise from creating and chaining a few promises. Take a moment to inspect the following bit of code. [source,javascript] ---- const p1 = fetch('/items') const p2 = p1.then(res => res.a.prop.that.does.not.exist) const p3 = p2.catch(err => {}) const p4 = p3.catch(err => console.error(err.message)) ---- Here is an enumeration of what is going on as that piece of code is executed: 1. `fetch` returns a brand new `p1` promise. 2. `p1.then` returns a brand new `p2` promise, which will react if `p1` is fulfilled. 3. `p2.catch` returns a brand new `p3` promise, which will react if `p2` is rejected. 4. `p3.catch` returns a brand new `p4` promise, which will react if `p3` is rejected. 5. When `p1` is fulfilled, the `p1.then` reaction is executed. 6. Afterwards, `p2` is rejected because of an error in the `p1.then` reaction. 7. Since `p2` was rejected, `p2.catch` reactions are executed, and the `p2.then` branch is ignored. 8. The `p3` promise from `p2.catch` is fulfilled, because it doesn't produce an error or result in a rejected promise. 9. Because `p3` was fulfilled, the `p3.catch` is never followed. The `p3.then` branch would've been used instead. You should ((("promises", "as tree structure")))think of promises as a tree structure. This bears repetition: you should think of promises as a tree structure.pass:[I wrote an online visualization tool called Promisees where you can see the tree structure underlying a Promise chain.] Let's reinforce this concept with <>. [[Fig4-4]] .Given the tree structure, we realize that p3 is fulfilled, as it doesn't produce an exception nor is it rejected. For that reason, p4 can never follow the rejection branch, given its parent was fulfilled. image::images/pmjs_0404.png["Promisees can help us visualize how the fetch promise is fulfilled, but p2 is rejected, thus triggering any rejection reactions attached to it. Given p3 is fulfilled, rejection reactions like p4 are never executed."] It all starts with a single promise, which we'll next learn how to construct. Then you add branches with `.then` or `.catch`. You can tack as many `.then` or `.catch` calls as you want onto each branch, creating new branches and ((("promises", "chaining", startref="p4c")))((("chaining", startref="ch4")))so on. [[creating_a_promise_from_scratch]] ==== Creating a Promise from Scratch We already ((("promises", "creating from scratch", id="p4cfs")))((("promises ", "resolving and rejecting", id="p4rar")))know that promises can be created using a function such as `fetch`, `Promise.resolve`, `Promise.reject`, or the `Promise` constructor function. We've already used `fetch` extensively to create promises in previous examples. Let's take a more nuanced look at the other three ways we can create a promise. Promises can be created from scratch by using `new Promise(resolver)`. The `resolver` parameter is a function that will be used to settle the promise. The `resolver` takes two arguments: a `resolve` function and a `reject` function. The pair of promises shown in the next snippet are settled in fulfillment and rejection, respectively. Here we're settling the first promise with a fulfillment value of `'result'`, and rejecting the second promise with an `Error` object, specifying `'reason'` as its message. [source,javascript] ---- new Promise(resolve => resolve('result')) new Promise((resolve, reject) => reject(new Error('reason'))) ---- Resolving and rejecting promises without a value is possible, but not that useful. Usually promises will fulfill with a `result` such as the response from an Ajax call, as we've seen with `fetch`. You'll definitely want to state the `reason` for your rejections--typically wrapping them in an `Error` object so that you can report back a stack trace. As you may have guessed, there's nothing inherently synchronous about promise resolvers. Settlement can be completely asynchronous for fulfillment and rejection alike. Even if the resolver calls `resolve` right away, the result won't trickle down to reactions until the next tick. That's the whole point of promises! The following example creates a promise that becomes fulfilled after two seconds elapse. [source,javascript] ---- new Promise(resolve => setTimeout(resolve, 2000)) ---- Note that only the first call made to one of these functions will have an impact--once a promise is settled its outcome can't change. The following code snippet creates a promise that's fulfilled after the provided `delay` or rejected after a three-second timeout. We're taking advantage of the fact that calling either of these functions after a promise has been settled has no effect, in order to create a race condition where the first call to be made will be the one that sticks. [source,javascript] ---- function resolveUnderThreeSeconds(delay) { return new Promise(function (resolve, reject) { setTimeout(resolve, delay) setTimeout(reject, 3000) }) } resolveUnderThreeSeconds(2000) // becomes fulfilled after 2s resolveUnderThreeSeconds(7000) // becomes rejected after 3s ---- When creating a new promise `p1`, you could call `resolve` with another promise ++p2++—besides calling `resolve` with nonpromise values. In those cases, `p1` will be resolved but blocked on the outcome of `p2`. Once `p2` settles, `p1` will be settled with its value and outcome. The following bit of code is, thus, effectively the same as simply doing `fetch('/items')`. [source,javascript] ---- new Promise(resolve => resolve(fetch('/items'))) ---- Note that this behavior is only possible when using `resolve`. If you try to replicate the same behavior with `reject` you'll find that the `p1` promise is rejected with the `p2` promise as the rejection `reason`. While `resolve` may result in a promise being fulfilled or rejected, `reject` always results in the promise being rejected. If you `resolve` to a rejected promise or a promise that's eventually rejected, then your promise will be rejected as well. The opposite isn't true for rejections. If you `reject` in a resolver, the promise will be rejected no matter what value is passed into `reject`. In some cases you'll know beforehand about a value you want to settle a promise with. In these cases you could create a promise from scratch, as shown next. This can be convenient when you want to set off the benefits of promise chaining, but don't otherwise have a clear initiator that returns a ++Promise++—such as a call to `fetch`. [source,javascript] ---- new Promise(resolve => resolve(12)) ---- That could prove to be too verbose when you don't need anything other than a pre-settled promise. You could use `Promise.resolve` instead, as a shortcut. The following statement is equivalent to the previous one. The differences between this statement and the previous one are purely semantics: you avoid declaring a `resolver` function and the syntax is more friendly to promise continuation and chaining when it comes to readability. [source,javascript] ---- Promise.resolve(12) ---- Like in the `resolve(fetch)` case we saw earlier, you could use `Promise.resolve` as a way of wrapping another promise or casting a thenable into a proper promise. The following piece of code shows how you could use `Promise.resolve` to cast a thenable into a proper promise and then consume it as if it were any other promise. [source,javascript] ---- Promise .resolve({ then: resolve => resolve(12) }) .then(x => console.log(x)) // <- 12 ---- When you already know the rejection reason for a promise, you can use `Promise.reject`. The following piece of code creates a promise that's going to settle into a rejection along with the specified `reason`. You can use `Promise.reject` within a reaction as a dynamic alternative to `throw` statements. Another use for `Promise.reject` is as an implicit return value for an arrow function, something that can't be done with a `throw` statements. [source,javascript] ---- Promise.reject(reason) fetch('/items').then(() => Promise.reject(new Error('arbitrarily')) ) fetch('/items').then(() => { throw new Error('arbitrarily')}) ---- Presumably, you won't be calling `new Promise` directly very often. The promise constructor is often invoked internally by libraries that support promises or native functions like `fetch`. Given that `.then` and `.catch` provide tree structures that unfold beyond the original promise, a single call to `new Promise` in the entry point to an API is often sufficient. Regardless, understanding promise creation is essential when leveraging promise-based control ((("promises", "creating from scratch", startref="p4cfs")))((("promises ", "resolving and rejecting", startref="p4rar")))flows. ==== Promise States and Fates Promises ((("promises", "states of", id="p4so")))can be in three distinct states: pending, fulfilled, and rejected. Pending is the default state. A promise can then transition into either fulfillment or rejection. A promise can be resolved or rejected exactly once. Attempting to resolve or reject a promise for a second time won't have any effect. When a promise is resolved with a nonpromise, nonthenable value, it settles in fulfillment. When a promise is rejected, it's also considered to be settled. A promise `p1` that's resolved to another promise or thenable `p2` stays in the pending state, but is nevertheless resolved: it can't be resolved again nor rejected. When `p2` settles, its outcome is forwarded to `p1`, which becomes settled as well. Once a promise is fulfilled, reactions that were attached with `p.then` will be executed as soon as possible. The same goes for rejected promises and `p.catch` reactions. Reactions attached after a promise is settled are also executed as soon as possible. The contrived example shown next could be used to explain how you can make a `fetch` request, and create a second `fetch` promise in a `.then` reaction to the first request. The second request will only begin when and if the first promise settles in fulfillment. The `console.log` statement will only begin when and if the second promise settles in fulfillment, printing `done` to the console. [source,javascript] ---- fetch('/items') .then(() => fetch('/item/first')) .then(() => console.log('done')) ---- A less contrived example would involve other steps. In the following piece of code we use the outcome of the first `fetch` request in order to construct the second request. To do that, we use the `res.json` method, which returns a promise that resolves to the object from parsing a JSON response. Then we use that object to construct the endpoint we want to request in our second call to `fetch`, and finally we print the `item` object from the second response to the console. [source,javascript] ---- fetch('/items') .then(res => res.json()) .then(items => fetch(`/item/${ items[0].slug }`)) .then(res => res.json()) .then(item => console.log(item)) ---- We're not limited to returning promises or thenables. We could also return values from `.then` and `.catch` reactions. Those values would be passed to the next reaction in the chain. In this sense, a reaction can be regarded as the transformation of input from the previous reaction in the chain into the input for the next reaction in the chain. The following example starts by creating a promise fulfilled with `[1, 2, 3]`. Then there's a reaction that maps those values into `[2, 4, 6]`. Those values are then printed to the console in the following reaction in the chain. [source,javascript] ---- Promise .resolve([1, 2, 3]) .then(values => values.map(value => value * 2)) .then(values => console.log(values)) // <- [2, 4, 6] ---- Note that you can transform data in rejection branches as well. Keep in mind that, as we first learned in <>, when a `.catch` reaction executes without errors and doesn't return a rejected promise either, it will fulfill, ((("promises", "states of", startref="p4so")))following `.then` reactions. ==== Promise#finally Proposal There's a ((("promises", "Promise#finally proposal", id="p4pf")))((("Promise#finally", id="pf4")))TC39 proposalpass:[This proposal is in stage 2 at the time of this writing. You can find the proposal draft at GitHub.] for a `Promise#finally` method, which would invoke a reaction when a promise settles, regardless of whether it was fulfilled or rejected. We can think of the following bit of code as a rough ponyfill for `Promise#finally`. We pass the reaction callback to `p.then` as both a fulfillment reaction and a rejection reaction. [source,javascript] ---- function finally(p, fn) { return p.then( fn, fn ) } ---- There are a few semantic differences involved. For one, reactions passed to `Promise#finally` don't receive any arguments, since the promise could've settled as either a fulfillment value or a rejection reason. Typically, `Promise#finally` variants in user-land are used for use cases such as hiding a loading spinner that was shown before a `fetch` request and other cleanup, where we don't need access to the promise's settlement value. The following snippet has an updated ponyfill which doesn't pass any arguments to either reaction. [source,javascript] ---- function finally(p, fn) { return p.then( () => fn(), () => fn() ) } ---- Reactions passed to `Promise#finally` resolve to the result of the parent promise. [source,javascript] ---- const p1 = Promise.resolve('value') const p2 = p1.finally(() => {}) const p3 = p2.then(data => console.log(data)) // <- 'value' ---- This is unlike `p.then(fn, fn)`, which would produce a new fulfillment value unless it's explicitly forwarded in the reaction, as shown next. [source,javascript] ---- const p1 = Promise.resolve('value') const p2 = p1.then(() => {}, () => {}) const p3 = p2.then(data => console.log(data)) // <- undefined ---- The following code listing has a complete ponyfill for `Promise#finally`. [source,javascript] ---- function finally(p, fn) { return p.then( result => resolve(fn()).then(() => result), err => resolve(fn()).then(() => Promise.reject(err)) ) } ---- Note that if the reaction passed to `Promise#finally` is rejected or throws, then the promise returned by `Promise#finally` will settle with that rejection reason, as shown next. [source,javascript] ---- const p1 = Promise.resolve('value') const p2 = p1.finally(() => Promise.reject('oops')) const p3 = p2.catch(err => console.log(err)) // <- 'oops' ---- As we can observe after carefully reading the code for our ponyfill, if either reaction results in an exception being thrown then the promise would be rejected. At the same time, returning a rejected promise via `Promise.reject` or some other means would imply `resolve(fn())` results in a rejected promise, which won't follow the `.then` reactions used to return the original settlement value of the promise ((("promises", "Promise#finally proposal", startref="p4pf")))((("Promise#finally", startref="pf4")))we're calling `.finally` on. ==== Leveraging Promise.all and Promise.race When ((("promises", "and concurrent tasks", secondary-sortas="concurrent", id="p4act")))((("Promise.race", id="prace4")))writing asynchronous code flows, there are pairs of tasks where one of them depends on the outcome of another, so they must run in series. There are also pairs of tasks that don't need to know the outcome of each other in order to run, so they can be executed concurrently. Promises already excel at asynchronous series flows, as a single promise can trigger a chain of events that happen one after another. Promises also offer a couple of solutions for concurrent tasks, in the form of two API ((("Promise.all", id="pall4")))methods: `Promise.all` and `Promise.race`. In most cases you'll want code that can be executed concurrently to take advantage of that, as it could make your code run much faster. Suppose you wanted to pull the description of two products in your catalog, using two distinct API calls, and then print out both of them to the console. The following piece of code would run both operations concurrently, but it would need separate print statements. In the case of printing to the console, that wouldn't make much of a difference, but if we needed to make single function call passing in both products, we couldn't do that with two separate `fetch` requests. [source,javascript] ---- fetch('/products/chair') .then(r => r.json()) .then(p => console.log(p)) fetch('/products/table') .then(r => r.json()) .then(p => console.log(p)) ---- The `Promise.all` method takes an array of promises and returns a single promise `p`. When all promises passed to `Promise.all` are fulfilled, `p` becomes fulfilled as well with an array of results sorted according to the provided promises. If a single promise becomes rejected, `p` settles with its rejection reason immediately. The following example uses `Promise.all` to fetch both products and print them to the console using a single `console.log` statement. [source,javascript] ---- Promise .all([ fetch('/products/chair'), fetch('/products/table') ]) .then(products => console.log(products[0], products[1])) ---- Given that the results are provided as an array, its indices have no semantic meaning to our code. Using parameter destructuring to pull out variable names for each product might make more sense when reading the code. The following example uses destructuring to clean that up. Keep in mind that even though there's a single argument, destructuring forces us to use parentheses in the arrow function parameter declaration. [source,javascript] ---- Promise .all([ fetch('/products/chair'), fetch('/products/table') ]) .then(([chair, table]) => console.log(chair, table)) ---- The following example shows how if a single promise is rejected, `p` will be rejected as well. It's important to understand that, as a single rejected promise might prevent an otherwise fulfilled array of promises from fulfilling `p`. In the example, rather than wait until `p2` and `p3` settle, `p` becomes immediately rejected. [source,javascript] ---- const p1 = Promise.reject('failed') const p2 = fetch('/products/chair') const p3 = fetch('/products/table') const p = Promise .all([p1, p2, p3]) .catch(err => console.log(err)) // <- 'failed' ---- In summary, `Promise.all` has three possible outcomes: - Settle with all fulfillment `results` as soon as all of its dependencies are fulfilled - Settle with a single rejection `reason` as soon as one of its dependencies is rejected - Stay in a pending state because at least one dependency stays in pending state and no dependencies are ((("Promise.all", startref="pall4")))rejected The `Promise.race` method is similar to `Promise.all`, except the first dependency to settle will "win" the race, and its result will be passed along to the promise returned by `Promise.race`. [source,javascript] ---- Promise .race([ new Promise(resolve => setTimeout(() => resolve(1), 1000)), new Promise(resolve => setTimeout(() => resolve(2), 2000)) ]) .then(result => console.log(result)) // <- 1 ---- Rejections will also finish the race, and the resulting promise will be rejected. Using `Promise.race` could be useful in scenarios where we want to time out a promise we otherwise have no control over. For instance, in the following piece of code there's a race between a `fetch` request and a promise that becomes rejected after a five-second timeout. If the request takes more than five seconds, the race will be rejected. [source,javascript] ---- function timeout(delay) { return new Promise(function (resolve, reject) { setTimeout(() => reject('timeout'), delay) }) } Promise .race([ fetch('/large-resource-download'), timeout(5000) ]) .then(res => console.log(res)) .catch(err => console.log(err)) ---- [[iterator_protocol_and_iterable_protocol]] === Iterator Protocol and Iterable Protocol JavaScript ((("promises", startref="prom4")))((("promises", "and concurrent tasks", secondary-sortas="concurrent", startref="p4act")))((("Promise.race", startref="prace4")))gets two new protocols in ES6: iterators and iterables. ((("iteration protocol", id="ip4")))These two protocols are used to define iteration behavior for any object. We'll start by learning about how to turn an object into an iterable sequence. Later, we'll look into laziness and how iterators can define infinite sequences. Lastly, we'll go over practical considerations while defining iterables. ==== Understanding Iteration Principles Any object can adhere to the iterable protocol by assigning a function to the `Symbol.iterator` property ((("Symbol.iterator", seealso="iteration protocols", id="si4")))for that object. Whenever an object needs to be iterated its iterable protocol method, assigned to `Symbol.iterator`, is called once. The ((("spread operator")))((("iteration protocol", "spread operator")))spread operator was first introduced in <>, and it's one of a few language features in ES6 that leverage iteration protocols. When using the spread operator on a hypothetical `iterable` object, as shown in the following code snippet, `Symbol.iterator` would be asked for an object that adheres to the iterator protocol. The returned iterator will be used to obtain values out of the object. [source,javascript] ---- const sequence = [...iterable] ---- As you might remember, symbol properties can't be directly embedded into object literal keys. The following bit of code shows how you'd add a symbol property using pre-ES6 language semantics. [source,javascript] ---- const example = {} example[Symbol.iterator] = fn ---- We could, however, use a computed property name to fit the symbol key in the object literal, avoiding an extra statement like the one in the previous snippet, as demonstrated next. [source,javascript] ---- const example = { [Symbol.iterator]: fn } ---- The method assigned to `Symbol.iterator` must return an object that adheres to the iterator protocol. That protocol defines how to get values out of an iterable sequence. The protocol dictates iterators must be objects with a `next` method. The `next` method takes no arguments and should return an object with these two properties found below: - `value` is the current item in the sequence - `done` is a Boolean indicating whether the sequence has ended Let's use the following piece of code as a crutch to understand the concepts behind iteration protocols. We're turning ((("iteration protocol", "sequence object", id="ip4so")))the `sequence` object into an iterable by adding a `Symbol.iterator` property. The iterable returns an iterator object. Each time `next` is asked for the following value in the sequence, an element from the `items` array is provided. When `i` goes beyond the last index on the `items` array, we return `done: true`, indicating the sequence has ended. [source,javascript] ---- const items = ['i', 't', 'e', 'r', 'a', 'b', 'l', 'e'] const sequence = { [Symbol.iterator]() { let i = 0 return { next() { const value = items[i] i++ const done = i > items.length return { value, done } } } } } ---- JavaScript is a progressive language: new features are additive, and they practically never break existing code. For that reason, iterables can't be taken advantage of in existing constructs such as `forEach` and `for..in`. In ES6, there are a few ways to go over iterables: `for..of`, the `...` spread operator, ((("iteration protocol", "Array.from method", id="ip4afm")))and `Array.from`. The `for..of` iteration method ((("iteration protocol", "for..of method", id="ip4fom")))can be used to loop over any iterable. The following example demonstrates how we could use `for..of` to loop over the `sequence` object we put together in the previous example, because it is an iterable object. [source,javascript] ---- for (const item of sequence) { console.log(item) // <- 'i' // <- 't' // <- 'e' // <- 'r' // <- 'a' // <- 'b' // <- 'l' // <- 'e' } ---- Regular objects can be made iterable with `Symbol.iterator`, as we've just learned. Under the ES6 paradigm, constructs like `Array`, `String`, `NodeList` in the DOM, and `arguments` are all iterable by default, giving `for..of` increased usability. To get an array out of any iterable sequence of values, you could use the spread operator, spreading every `item` in the sequence onto an element in the resulting array. We could also use `Array.from` to the same effect. In addition, `Array.from` can also cast array-like objects, those with a `length` property and items in zero-based integer properties, into arrays. [source,javascript] ---- console.log([...sequence]) // <- ['i', 't', 'e', 'r', 'a', 'b', 'l', 'e'] console.log(Array.from(sequence)) // <- ['i', 't', 'e', 'r', 'a', 'b', 'l', 'e'] console.log(Array.from({ 0: 'a', 1: 'b', 2: 'c', length: 3 })) // <- ['a', 'b', 'c'] ---- As a recap, ((("iteration protocol", "sequence object", startref="ip4so")))((("iteration protocol", "iterator/iterable distinction")))the `sequence` object adheres to the iterable protocol by assigning a method to `[Symbol.iterator]`. That means that the object is iterable: it can be iterated. Said method returns an object that adheres to the `iterator` protocol. The iterator method is called once whenever we need to start iterating over the object, and the returned iterator is used to pull values out of `sequence`. To iterate over iterables, we can use `for..of`, the ((("spread operator")))((("iteration protocol", "spread operator")))spread operator, ((("iteration protocol", "Array.from method", startref="ip4afm")))or `Array.from`. In essence, ((("Symbol.iterator", seealso="iteration protocols", startref="si4")))the selling point about these protocols is that they provide expressive ways to effortlessly iterate over collections and array-likes. Having the ability to define how any object may be iterated is huge, because it enables libraries to converge under a protocol the language natively understands: iterables. The upside is that implementing the iterator protocol doesn't have a high-effort cost because, due to its additive nature, it won't break existing behavior. For example, jQuery and `document.querySelectorAll` both return array-likes. If jQuery implemented the iterator protocol on their collection's prototype, then you could iterate over collection elements using the native `for..of` construct. [source,javascript] ---- for (const element of $('li')) { console.log(element) // <- a
  • in the jQuery collection } ---- Iterable sequences ((("iteration protocol", "for..of method", startref="ip4fom")))aren't necessarily finite. They may have an uncountable amount of elements. Let's delve into that topic and its implications. [[infinite_sequences]] ==== Infinite Sequences Iterators are lazy in nature. ((("iteration protocol", "infinite sequences", id="ip4is")))((("infinite sequences", id="is4")))Elements in an iterator sequence are generated one at a time, even when the sequence is finite. Note that infinite sequences couldn't be represented without the laziness property. An infinite sequence can't be represented as an array, meaning that using the spread operator or `Array.from` ((("Array.from")))to cast a sequence into an array would crash JavaScript execution, as we'd go into an infinite loop. The following example shows an iterator that represents an infinite sequence of random floating numbers between 0 and 1. Note how items returned by `next` don't ever have a `done` property set to `true`, which would signal that the sequence has ended. It uses a pair of arrow functions that implicitly return objects. The first one returns the iterator object used to loop over the infinite sequence of random numbers. The second arrow function is used to pull each individual value in the sequence, using `Math.random`. [source,javascript] ---- const random = { [Symbol.iterator]: () => ({ next: () => ({ value: Math.random() }) }) } ---- Attempting to cast the iterable `random` object into an array using either `Array.from(random)` or `[...random]` would crash our program, since the sequence never ends. We must be very careful with these types of sequences as they can easily crash and burn our browser and Node.js server processes. There are a few different ways you can access a sequence safely, without risking an infinite loop. The first option is to use ((("destructuring")))((("infinite sequences", "destructuring")))destructuring to pull values in specific positions of the sequence, as shown in the following piece of code. [source,javascript] ---- const [one, another] = random console.log(one) // <- 0.23235511826351285 console.log(another) // <- 0.28749457537196577 ---- Destructuring infinite sequences doesn't scale very well, particularly if we want to apply dynamic conditions, such as pulling the first `i` values out of the sequence or pulling values until we find one that doesn't match a condition. In those cases we're better off using `for..of`, ((("for..of", id="fo4")))where we're better able to define conditions that prevent infinite loops while taking as many elements as we need, in a programmatic fashion. The next example loops over our infinite sequence using `for..of`, but it breaks the loop as soon as a value is higher than `0.8`. Given that `Math.random` produces values anywhere between `0` and `1`, the loop will eventually break. [source,javascript] ---- for (const value of random) { if (value > 0.8) { break } console.log(value) } ---- It can be hard to understand code like that when reading it later, as a lot of the code is focused on how the sequence is iterated, printing values from `random` until one of those values is large enough. Abstracting away part of the logic into another method might make the code more readable. As another example, a common pattern when extracting values from an infinite or very large sequence is to "take" the first few elements in the sequence. While you could accommodate that use case through `for..of` and `break`, you'd be better off abstracting it into a `take` ((("take")))method. The following example shows a potential implementation of `take`. It receives a `sequence` parameter and the `amount` of entries you'd like to take from the `sequence`. It returns an iterable object, and whenever that object is iterated it constructs an iterator for the provided `sequence`. The `next` ((("next")))method defers to the original `sequence` while the `amount` is at least `1`, and then ends the sequence. [source,javascript] ---- function take(sequence, amount) { return { [Symbol.iterator]() { const iterator = sequence[Symbol.iterator]() return { next() { if (amount-- < 1) { return { done: true } } return iterator.next() } } } } } ---- Our implementation works great on infinite sequences because it provides them with a constant exit condition: whenever the `amount` counter is depleted, the sequence returned by `take` ends. Instead of looping to pull values out of `random`, you can now write a piece of code like the following. [source,javascript] ---- [...take(random, 2)] // <- [0.304253100650385, 0.5851333604659885] ---- This pattern allows you to reduce any infinite sequence into a finite one. If your desired finite sequence wasn't just "the first N values," but rather our original "all values before the first one larger than 0.8," you could easily adapt `take` by changing its exit condition. The `range` function shown next has a `low` parameter that defaults to `0`, and a `high` parameter defaulting to `1`. Whenever a value in the sequence is out of bounds, we stop pulling values from it. [source,javascript] ---- function range(sequence, low = 0, high = 1) { return { [Symbol.iterator]() { const iterator = sequence[Symbol.iterator]() return { next() { const item = iterator.next() if (item.value < low || item.value > high) { return { done: true } } return item } } } } } ---- Now, instead of breaking in the `for..of` loop because we fear that the infinite sequence will never end, we guaranteed that the loop will eventually break outside of our desired range. This way, your code becomes less concerned with how the sequence is generated, and more concerned with what the sequence will be used for. As shown in the following example, you won't even need a `for..of` loop here either, because the escape condition now resides in the intermediary `range` function. [source,javascript] ---- const low = [...range(random, 0, 0.8)] // <- [0.68912092433311, 0.059788614744320, 0.09396195202134] ---- This sort of abstraction of complexity into another function often helps keep code focused on its intent, while striving to avoid a `for..of` loop when all we wanted was to produce a derivated sequence. It also shows how sequences can be composed and piped into one another. In this case, we first created a multipurpose and infinite `random` sequence, and then piped it through a `range` function that returns a derivated sequence that ends when it meets values that are below or above a desired range. An important aspect of iterators is that despite having been composed, the iterators produced by the `range` function can be lazily iterated as well, effectively meaning you can compose as many iterators you need into mapping, filtering, and exit condition helpers. [role="pagebreak-before"] .Identifying Infinite Sequences **** Iterators ((("infinite sequences", "identifying")))don't have any knowledge that the sequences they produce are infinite. In a similar situation to the famous halting problem (<>), there is no way of knowing whether the sequence is infinite or not in code. [[fig0405]] .The halting problem depicted in an https://mjavascript.com/out/xkcd-1266[XKCD comic] image::images/pmjs_0405.png["The halting problem depicted in an XKCD comic: https://mjavascript.com/out/xkcd-1266."] You typically have a good idea of whether a sequence is infinite or not. Whenever you have an infinite sequence it's up to you to add an escape condition that ensures the program won't crash in an attempt to loop over every single value in the sequence. While `for..of` won't ((("for..of", startref="fo4")))run into the problem unless there's no escape condition, using mechanisms such as spread or `Array.from` would immediately result in the program crashing into an infinite loop in the case of infinite sequences. **** Besides the technical implications of creating iterable objects, let's go over a couple of practical examples on how we can ((("iteration protocol", "infinite sequences", startref="ip4is")))((("infinite sequences", startref="is4")))benefit from iterators. ==== Iterating Object Maps as Key/Value Pairs There's an ((("iteration protocol", "object maps as key/value pairs", id="ip4omakvp")))((("object maps", id="om4")))abundance of practical situations that benefit from turning an object into an iterable. Object maps, pseudoarrays that are meant to be iterated, the random number generator we came up with in <>, and classes or plain objects with properties that are often iterated could all turn a profit from following the iterable protocol. Oftentimes, JavaScript objects are used to represent a map between string keys and arbitrary values. In the next snippet, as an example, we have a map of color names and hexadecimal RGB representations of that color. There are cases when you'd welcome the ability to effortlessly loop over the different color names, hexadecimal representations, or key/value pairs. [source,javascript] ---- const colors = { green: '#0e0', orange: '#f50', pink: '#e07' } ---- The following code snippet implements an iterable that produces a `[key, value]` sequence for each color in the `colors` map. Given that that's assigned to the `Symbol.iterator` ((("Symbol.iterator", id="si4x")))property, we'd be able to go over the list with minimal effort. [source,javascript] ---- const colors = { green: '#0e0', orange: '#f50', pink: '#e07', [Symbol.iterator]() { const keys = Object.keys(colors) return { next() { const done = keys.length === 0 const key = keys.shift() return { done, value: [key, colors[key]] } } } } } ---- When we wanted to pull out all the key/value pairs, we could use the `...` spread operator as shown in the following bit of code. [source,javascript] ---- console.log([...colors]) // <- [['green', '#0e0'], ['orange', '#f50'], ['pink', '#e07']] ---- The fact that we're polluting our previously tiny `colors` map with a large iterable definition could represent a problem, as the iterable behavior has little to do with the concern of storing pairs of color names and codes. A good way of decoupling the two aspects of `colors` would be to extract the logic that attaches a key/value pair iterator into a reusable function. This way, we could eventually move `keyValueIterable` somewhere else in our codebase and leverage it for other use cases as well. [source,javascript] ---- function keyValueIterable(target) { target[Symbol.iterator] = function () { const keys = Object.keys(target) return { next() { const done = keys.length === 0 const key = keys.shift() return { done, value: [key, target[key]] } } } } return target } ---- We could then call `keyValueIterable` passing in the `colors` object, turning `colors` into an iterable object. You could in fact use `keyValueIterable` on any objects where you want to iterate over key/value pairs, as the iteration behavior doesn't make assumptions about the object. Once we've attached a `Symbol.iterator` behavior, we'll be able to treat the object as an iterable. ((("Symbol.iterator", startref="si4x")))In the next code snippet, we iterate over the key/value pairs and print only the color codes. [source,javascript] ---- const colors = keyValueIterable({ green: '#0e0', orange: '#f50', pink: '#e07' }) for (const [ , color] of colors) { console.log(color) // <- '#0e0' // <- '#f50' // <- '#e07' } ---- A song player might be another ((("object maps", startref="om4")))((("iteration protocol", "object maps as key/value pairs", startref="ip4omakvp")))interesting use case. ==== Building Versatility Into Iterating a Playlist Imagine ((("iteration protocol", "iterating a playlist", id="ip4iap")))you were developing a song player where a playlist could be reproduced once and then stopped, or be put on repeat and reproduced indefinitely. Whenever you have a use case of looping through a list indefinitely, you could leverage the iterable protocol as well. Suppose a human adds a few songs to her library, and they are stored in an array as shown in the next bit of code. [source,javascript] ---- const songs = [ 'Bad moon rising – Creedence', 'Don’t stop me now – Queen', 'The Scientist – Coldplay', 'Somewhere only we know – Keane' ] ---- We could create a `playlist` function that returns a sequence, representing all the songs that will be played by our application. This function would take the `songs` provided by the human as well as the `repeat` value, which indicates how many times she wants the songs to be reproduced in a loop--once, twice, or `Infinity` times--before coming to an end. The following piece of code shows how we could implement `playlist`. We start with an empty playlist and use an `index` number to track where in the song list we are positioned. We return the next song in the list by incrementing the `index`, until there aren't any songs left in the current loop. At this point we decrement the `repeat` counter and reset the `index`. The sequence ends when there aren't any songs left and `repeat` reaches zero. [source,javascript] ---- function playlist(songs, repeat) { return { [Symbol.iterator]() { let index = 0 return { next() { if (index >= songs.length) { repeat-- index = 0 } if (repeat < 1) { return { done: true } } const song = songs[index] index++ return { done: false, value: song } } } } } } ---- The following bit of code shows how the `playlist` function can take an array and produce a sequence that goes over the provided array for the specified amount of times. If we specified `Infinity`, the resulting sequence would be infinite, and otherwise it'd be finite. [source,javascript] ---- console.log([...playlist(['a', 'b'], 3)]) // <- ['a', 'b', 'a', 'b', 'a', 'b'] ---- To iterate over the playlist we'd probably come up with a `player` function. Assuming a `playSong` function that reproduces a song and invokes a callback when the song ends, our `player` implementation could look like the following function, where we asynchronously loop the iterator coming from a sequence, requesting new songs as previous ones finish playback. Given that there's always a considerable waiting period in between `g.next` calls--while the songs are actually playing inside ++playSong++—there's little risk of being stuck in an infinite loop that'd crash the runtime, even when the sequence produced by `playlist` is infinite. [source,javascript] ---- function player(sequence) { const g = sequence() more() function more() { const item = g.next() if (item.done) { return } playSong(item.value, more) } } ---- Putting everything together, the music library would play a song list on repeat with a few lines of code, as presented in the next code snippet. [source,javascript] ---- const songs = [ 'Bad moon rising – Creedence', 'Don’t stop me now – Queen', 'The Scientist – Coldplay', 'Somewhere only we know – Keane' ] const sequence = playlist(songs, Infinity) player(sequence) ---- A change allowing the human to shuffle her playlist wouldn't be complicated to introduce. We'd have to tweak the `playlist` function to include a `shuffle` flag, and if that flag is present we'd sort the song list at random. [source,javascript] ---- function playlist(inputSongs, repeat, shuffle) { const songs = shuffle ? shuffleSongs(inputSongs) : inputSongs return { [Symbol.iterator]() { let index = 0 return { next() { if (index >= songs.length) { repeat-- index = 0 } if (repeat < 1) { return { done: true } } const song = songs[index] index++ return { done: false, value: song } } } } } } function shuffleSongs(songs) { return songs.slice().sort(() => Math.random() > 0.5 ? 1 : -1) } ---- Lastly, we'd have to pass in the `shuffle` flag as `true` if we wanted to shuffle songs in the playlist. Otherwise, songs would be reproduced in the original order provided by the user. Here again we've abstracted away something that usually would involve many lines of code used to decide what song comes next into a neatly decoupled function that's only concerned with producing a sequence of songs to be reproduced by a song player. [source,javascript] ---- console.log([...playlist(['a', 'b'], 3, true)]) // <- ['a', 'b', 'b', 'a', 'a', 'b'] ---- You may have noticed how the `playlist` function doesn't necessarily need to concern itself with the sort order of the songs passed to it. A better design choice may well be to extract shuffling into the calling code. If we kept the original `playlist` function without a `shuffle` parameter, we could still use a snippet like the following to obtain a shuffled song collection. [source,javascript] ---- function shuffleSongs(songs) { return songs.slice().sort(() => Math.random() > 0.5 ? 1 : -1) } console.log([...playlist(shuffleSongs(['a', 'b']), 3)]) // <- ['a', 'b', 'b', 'a', 'a', 'b'] ---- Iterators are an important tool in ES6 that help us not only to decouple code, but also to come up with constructs that were previously harder to implement, such as the ability of dealing with a sequence of songs indistinctly--regardless of whether the sequence is finite or infinite. This indifference is, in part, what makes writing code leveraging the iterator protocol more elegant. It also makes it risky to cast an unknown iterable into an array (with, say, the `...` spread operator), as you're risking crashing your program due to an infinite loop. Generators are an alternative way of creating functions that return an iterable object, without explicitly declaring an object literal with a `Symbol.iterator` method. They make it easier to implement functions, such as the `range` or `take` functions in <>, while also allowing for a few more interesting use ((("iteration protocol", "iterating a playlist", startref="ip4iap")))((("iteration protocol", startref="ip4")))cases. === Generator Functions and Generator Objects Generators ((("generators", id="gen4")))are a new feature in ES6. The way they work is that you declare a generator function that returns generator objects `g`. Those `g` objects can then be iterated using any of `Array.from(g)`, `[...g]`, or `for..of` loops. Generator functions allow you to declare a special kind of `iterator`. These iterators can suspend execution while retaining their context. ==== Generator Fundamentals We ((("generators", "fundamentals", id="gen4f")))already examined iterators in the previous section, learning how their `.next()` method is called one at a time to pull values from a sequence. Instead of a `next` method whenever you return a value, generators use the `yield` ((("yield")))keyword to add values into the sequence. Here is an example generator function. Note the pass:[*] after `function`. That's ((("function*")))not a typo, that's how you mark a generator function as a generator. [source,javascript] ---- function* abc() { yield 'a' yield 'b' yield 'c' } ---- Generator ((("iteration protocol", "in generators", see="generators", secondary-sortas="generators")))objects conform to both the iterable protocol and the iterator protocol: - A generator object `chars` is built using the `abc` function - Object `chars` is an iterable because it has a `Symbol.iterator` method - Object `chars` is also an iterator because it has a `.next` method - The iterator for `chars` is itself The same statements can also be demonstrated using JavaScript code. [source,javascript] ---- const chars = abc() typeof chars[Symbol.iterator] === 'function' typeof chars.next === 'function' chars[Symbol.iterator]() === chars console.log(Array.from(chars)) // <- ['a', 'b', 'c'] console.log([...chars]) // <- ['a', 'b', 'c'] ---- When you create a generator object, you'll get an iterator that uses the generator function to produce an iterable sequence. Whenever a `yield` expression is reached, its value is emitted by the iterator and generator function execution becomes suspended. The following example shows how iteration can trigger side effects within the generator function. The `console.log` ((("console.log")))statements after each `yield` statement will be executed when generator function execution becomes unsuspended and asked for the next element in the sequence. [source,javascript] ---- function* numbers() { yield 1 console.log('a') yield 2 console.log('b') yield 3 console.log('c') } ---- Suppose you created a generator object for `numbers`, spread its contents onto an array, and printed it to the console. Taking into account the side effects in `numbers`, can you guess what the console output would look like for the following piece of code? Given that the spread operator iterates over the sequence to completion in order to give you an array, all side effects would be executed while constructing the array via destructuring, before the `console.log` statement printing the array is ever reached. [source,javascript] ---- console.log([...numbers()]) // <- 'a' // <- 'b' // <- 'c' // <- [1, 2, 3] ---- If we now used a `for..of` loop ((("for..of")))instead, we'd be able to preserve the order declared in the `numbers` generator function. In the next example, elements in the `numbers` sequence are printed one at a time in a `for..of` loop. The first time the generator function is asked for a `number`, it yields `1` and execution becomes suspended. The second time, execution is unsuspended where the generator left off, `'a'` is printed to the console as a side effect, and `2` is yielded. The third time, `'b'` is the side effect, and `3` is yielded. The fourth time, `'c'` is a side effect and the generator signals that the sequence has ended. [source,javascript] ---- for (const number of numbers()) { console.log(number) // <- 1 // <- 'a' // <- 2 // <- 'b' // <- 3 // <- 'c' } ---- .Using yield* to Delegate Sequence Generation **** Generator functions can use `yield*` to ((("yield*", id="y4")))delegate to a generator object or any other iterable object. Given that strings in ES6 adhere to the iterable protocol, you could write a piece of code like the following to split `hello` into individual characters. [source,javascript] ---- function* salute() { yield* 'hello' } console.log([...salute()]) // <- ['h', 'e', 'l', 'l', 'o'] ---- Naturally, you could use `[...'hello']` as a simpler alternative. However, it's when combining multiple `yield` statements that we'll start to see the value in delegating to another iterable. The next example shows a `salute` generator modified into taking a `name` parameter and producing array that contains the characters for the `'hello you'` string. [source,javascript] ---- function* salute(name) { yield* 'hello ' yield* name } console.log([...salute('you')]) // <- ['h', 'e', 'l', 'l', 'o', ' ', 'y', 'o', 'u'] ---- To reiterate, you can `yield*` anything that adheres to the iterable protocol, not merely strings. That includes generator objects, arrays, `arguments`, `NodeList` in the browser, and just about anything, provided it implements `Symbol.iterator`. The following example demonstrates how you could mix `yield` and `yield*` statements to describe a sequence of values using generator functions, an iterable object, and the spread operator. Can you deduce what the `console.log` statement would print? [source,javascript] ---- const salute = { [Symbol.iterator]() { const items = ['h', 'e', 'l', 'l', 'o'] return { next: () => ({ done: items.length === 0, value: items.shift() }) } } } function* multiplied(base, multiplier) { yield base + 1 * multiplier yield base + 2 * multiplier } function* trailmix() { yield* salute yield 0 yield* [1, 2] yield* [...multiplied(3, 2)] yield [...multiplied(6, 3)] yield* multiplied(15, 5) } console.log([...trailmix()]) ---- Here's the sequence produced by the `trailmix` generator ((("yield*", startref="y4")))function. [source,javascript] ---- ['h', 'e', 'l', 'l', 'o', 0, 1, 2, 5, 7, [9, 12], 20, 25] ---- **** Besides iterating over a generator object using spread, `for..of`, and `Array.from`, we could use the generator object directly, and iterate over that. Let's investigate how ((("generators", "fundamentals", startref="gen4f")))that'd work. ==== Iterating over Generators by Hand Generator iteration ((("generators", "iterating over by hand", id="gen4iobh")))isn't limited to `for..of`, `Array.from`, or the spread operator. Just like with any iterable object, you can use its `Symbol.iterator` to pull values on demand using `.next`, rather than in a strictly synchronous `for..of` loop or all at once with `Array.from` or spread. Given that a generator object is both iterable and iterator, you won't need to call `g[Symbol.iterator]()` to get an iterator: you can use `g` directly because it's the same object as the one returned by the `Symbol.iterator` method. Assuming the `numbers` iterator we created earlier, the following example shows how you could iterate it by hand using the generator object and a `while` loop. Remember that any items returned by an iterator need a `done` property that indicates whether the sequence has ended, and a `value` property indicating the current value in the sequence. [source,javascript] ---- const g = numbers() while (true) { const item = g.next() if (item.done) { break } console.log(item.value) } ---- Using iterators to loop over a generator might look like a complicated way of implementing a `for..of` loop, but it also allows for some interesting use cases. Particularly: `for..of` ((("for..of")))is always a synchronous loop, whereas with iterators we're in charge of deciding when to invoke `g.next`. In turn, that translates into additional opportunities such as running an asynchronous operation and then calling `g.next` once we have a result. Whenever `.next()` is (((".next()", primary-sortas="next")))(((".next()")))called on a generator, there are four different kinds of "events" that can suspend execution in the generator while returning a result to the caller of `.next()`. We'll promptly explore each of these scenarios: - A `yield` expression ((("yield")))returning the next value in the sequence - A `return` statement ((("return")))returning the last value in the sequence - A `throw` statement ((("throw")))halts execution in the generator entirely - Reaching the end of the generator function signals `{ done: true }`, as the function implicitly returns `undefined` Once the `g` generator finishes iterating over a sequence, subsequent calls to `g.next()` will have no effect and just return `{ done: true }`. The following code snippet demonstrates the idempotence we can observe when calling `g.next` repeatedly once a sequence has ended. [source,javascript] ---- function* generator() { yield 'only' } const g = generator() console.log(g.next()) // <- { done: false, value: 'only' } console.log(g.next()) // <- { done: true } console.log(g.next()) // <- { done: true } ---- ==== Mixing Generators into Iterables Let's ((("generators", "iterating over by hand", startref="gen4iobh")))((("generators", "mixing into iterables", id="gen4mii")))do a quick recap of generators. Generator functions return generator objects when invoked. A generator object has a `next` method, which returns the next element in the sequence. The `next` ((("next")))method returns objects with a `{ value, done }` shape. The following example shows an infinite Fibonacci number generator. We then instantiate a generator object and read the first eight values in the sequence. [source,javascript] ---- function* fibonacci() { let previous = 0 let current = 1 while (true) { yield current const next = current + previous previous = current current = next } } const g = fibonacci() console.log(g.next()) // <- { value: 1, done: false } console.log(g.next()) // <- { value: 1, done: false } console.log(g.next()) // <- { value: 2, done: false } console.log(g.next()) // <- { value: 3, done: false } console.log(g.next()) // <- { value: 5, done: false } console.log(g.next()) // <- { value: 8, done: false } console.log(g.next()) // <- { value: 13, done: false } console.log(g.next()) // <- { value: 21, done: false } ---- Iterables follow a similar pattern. They enforce a contract that dictates we should return an object with a `next` method. That method should return sequence elements following a `{ value, done }` shape. The following example shows a `fibonacci` iterable that's a rough equivalent of the generator we were just looking at. [source,javascript] ---- const fibonacci = { [Symbol.iterator]() { let previous = 0 let current = 1 return { next() { const value = current const next = current + previous previous = current current = next return { value, done: false } } } } } const sequence = fibonacci[Symbol.iterator]() console.log(sequence.next()) // <- { value: 1, done: false } console.log(sequence.next()) // <- { value: 1, done: false } console.log(sequence.next()) // <- { value: 2, done: false } console.log(sequence.next()) // <- { value: 3, done: false } console.log(sequence.next()) // <- { value: 5, done: false } console.log(sequence.next()) // <- { value: 8, done: false } console.log(sequence.next()) // <- { value: 13, done: false } console.log(sequence.next()) // <- { value: 21, done: false } ---- Let's reiterate. An iterable should return an object with a `next` method: generator functions do just that. The `next` method should return objects with a `{ value, done }` shape: generator functions do that too. What happens if we change the `fibonacci` iterable to use a generator function for its `Symbol.iterator` property? As it turns out, it just works. The following example shows the iterable `fibonacci` object using a generator function to define how it will be iterated. Note how that iterable has the exact same contents as the `fibonacci` generator function we saw earlier. We can use `yield`, `yield*`, and all of the semantics found in generator functions hold. [source,javascript] ---- const fibonacci = { * [Symbol.iterator]() { let previous = 0 let current = 1 while (true) { yield current const next = current + previous previous = current current = next } } } const g = fibonacci[Symbol.iterator]() console.log(g.next()) // <- { value: 1, done: false } console.log(g.next()) // <- { value: 1, done: false } console.log(g.next()) // <- { value: 2, done: false } console.log(g.next()) // <- { value: 3, done: false } console.log(g.next()) // <- { value: 5, done: false } console.log(g.next()) // <- { value: 8, done: false } console.log(g.next()) // <- { value: 13, done: false } console.log(g.next()) // <- { value: 21, done: false } ---- Meanwhile, the iterable protocol also holds up. To verify that, you might use a construct like `for..of`, instead of manually creating the generator object. The following example uses `for..of` and ((("for..of")))introduces a circuit breaker to prevent an infinite loop from crashing the program. [source,javascript] ---- for (const value of fibonacci) { console.log(value) if (value > 20) { break } } // <- 1 // <- 1 // <- 2 // <- 3 // <- 5 // <- 8 // <- 13 // <- 21 ---- Moving onto more practical examples, let's see how generators can help us iterate tree data structures concisely. ==== Tree Traversal Using Generators Algorithms ((("generators", "in tree traversal", secondary-sortas="tree", id="gen4itt")))((("tree traversal", id="tt4")))to work with tree structures can be tricky to understand, often involving recursion. Consider the following bit of code, where we define a `Node` class that can hold a `value` and an arbitrary amount of child nodes. [source,javascript] ---- class Node { constructor(value, ...children) { this.value = value this.children = children } } ---- Trees can be traversed using depth-first search, where we always try to go deeper into the tree structure, and when we can't move to the next children on the list. In the following tree structure, a depth-first search algorithm would traverse the tree visiting the nodes following the `1, 2, 3, 4, 5, 6, 7, 8, 9, 10` order. [source,javascript] ---- const root = new Node(1, new Node(2), new Node(3, new Node(4, new Node(5, new Node(6) ), new Node(7) ) ), new Node(8, new Node(9), new Node(10) ) ) ---- One way of implementing depth-first traversal for our tree would be using a generator function that yields the current node's value, and then iterates over its children yielding every item in their sequences using the `yield*` operator as a way of composing the recursive component of the iterator. [source,javascript] ---- function* depthFirst(node) { yield node.value for (const child of node.children) { yield* depthFirst(child) } } console.log([...depthFirst(root)]) // <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ---- A slightly different way of declaring the traversal algorithm would be to make the `Node` class iterable using the `depthFirst` generator. The following piece of code also takes advantage that `child` is a `Node` class--and thus an iterable--using `yield*` in order to yield the iterable sequence for that `child` as part of the sequence for its parent node. [source,javascript] ---- class Node { constructor(value, ...children) { this.value = value this.children = children } * [Symbol.iterator]() { yield this.value for (const child of this.children) { yield* child } } } console.log([...root]) // <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ---- If we wanted to change traversal to a breadth-first algorithm, we could change the iterator into an algorithm like the one in the following piece of code. Here, we use a first-in first-out queue to keep a buffer of nodes we haven't visited yet. In each step of the iteration, starting with the `root` node, we print the current node's `value` and push its children onto the queue. Children are always added to the end of the queue, but we pull items from the beginning of the queue. That means we'll always go through all the nodes at any given depth before going deeper into the tree structure. [source,javascript] ---- class Node { constructor(value, ...children) { this.value = value this.children = children } * [Symbol.iterator]() { const queue = [this] while (queue.length) { const node = queue.shift() yield node.value queue.push(...node.children) } } } console.log([...root]) // <- [1, 2, 3, 8, 4, 9, 10, 5, 7, 6] ---- Generators are useful due to their expressiveness, while the iterator protocol allows us to define a sequence we can iterate at our own pace, which comes in handy when a tree has thousands of nodes and we need to throttle iteration for performance ((("generators", "in tree traversal", secondary-sortas="tree", startref="gen4itt")))((("tree traversal", startref="tt4")))reasons. ==== Consuming Generator Functions for Flexibility Thus ((("generators", "consuming generator functions for flexibility", id="gen4cgfff")))far in the chapter we've talked about generators in terms of constructing a consumable sequence. Generators can also be presented as an interface to a piece of code that decides how the generator function is to be iterated over. In this section, we'll be writing a generator function that gets passed to a method, which loops over the generator consuming elements of its sequence. Even though you might think that writing code like this is unconventional at first, most libraries built around generators have their users write the generators while the library retains control over the iteration. The following bit of code could be used as an example of how we'd like `modelProvider` to work. The consumer provides a generator function that yields crumbs to different parts of a model, getting back the relevant part of the model each time. A generator object can pass results back to the generator function by way of `g.next(result)`. When we do this, a `yield` expression evaluates to the `result` produced by the generator object. [source,javascript] ---- modelProvider(function* () { const items = yield 'cart.items' const item = items.reduce( (left, right) => left.price > right.price ? left : right ) const details = yield `products.${ item.id }` console.log(details) }) ---- Whenever a resource is yielded by the user-provided generator, execution in the generator function is suspended until the iterator calls `g.next` again, which may even happen asynchronously behind the scenes. The next code snippet implements a `modelProvider` function that iterates over `paths` yielded by the generator. Note also how we're passing `data` to `g.next()`. [source,javascript] ---- const model = { cart: { items: [item1, …, itemN] }, products: { product1: { … }, productN: { … } } } function modelProvider(paths) { const g = paths() pull() function pull(data) { const { value, done } = g.next(data) if (done) { return } const crumbs = value.split('.') const data = crumbs.reduce(followCrumbs, model) pull(data) } } function followCrumbs(data, crumb) { if (!data || !data.hasOwnProperty(crumb)) { return null } return data[crumb] } ---- The largest benefit of asking consumers to provide a generator function is that providing them with the `yield` keyword opens up a world of possibilities where execution in their code may be suspended while the iterator performs an asynchronous operation in between `g.next` calls. Let's explore more asynchronous uses of generators in the ((("generators", "consuming generator functions for flexibility", startref="gen4cgfff")))next section. ==== Dealing with Asynchronous Flows Going ((("generators", "asynchronous flows", id="gen4af")))((("asynchronous code", id="ac4")))back to the example where we call `modelProvider` with a user-provided generator, let's consider what would change about our code if the model parts were to be provided asynchronously. The beauty of generators is that if the way we iterate over the sequence of paths were to become asynchronous, the user-provided function wouldn't have to change at all. We already have the ability to suspend execution in the generator while we fetch a piece of the model, and all it'd take would be to ask a service for the answer to the current path, return that value via an intermediary `yield` statement or in some other way, and then call `g.next` on the generator object. Let's assume we're back at the following usage of `modelProvider`. [source,javascript] ---- modelProvider(function* () { const items = yield 'cart.items' const item = items.reduce( (left, right) => left.price > right.price ? left : right ) const details = yield `products.${ item.id }` console.log(details) }) ---- We'll be using `fetch` to make requests for each HTTP resource--which, as you may recall, returns a `Promise`. Note that given an asynchronous scenario we can't use `for..of` to go over the sequence, which is limited to synchronous loops. The next code snippet sends an HTTP request for each query to the model, and the server is now in charge of producing the relevant bits of the model, without the client having to keep any state other than the relevant user authentication bits, such as cookies. [source,javascript] ---- function modelProvider(paths) { const g = paths() pull() function pull(data) { const { value, done } = g.next(data) if (done) { return } fetch(`/model?query=${ encodeURIComponent(value) }`) .then(response => response.json()) .then(data => pull(data)) } } ---- Always keep in mind that, while a `yield` expression is being evaluated, execution of the generator function is paused until the next item in the sequence--the next query for the model, in our example--is requested to the iterator. In this sense, code in a generator function looks and feels as if it were synchronous, even though `yield` pauses execution in the generator until `g.next` resumes execution. While generators let us write asynchronous code that appears synchronous, this introduces an inconvenience. How do we handle errors that arise in the iteration? If an HTTP request fails, for instance, how do we notify the generator and then handle the error notification in the ((("generators", "asynchronous flows", startref="gen4af")))((("generators", "asynchronous flows", startref="gen4af")))generator function? ==== Throwing Errors at a Generator Before ((("generators", "throwing errors at", id="gen4tea")))shifting our thinking into user-provided generators, where they retain control of seemingly synchronous functions thanks to `yield` ((("yield", id="y4x")))and suspension, we would've been hard pressed to find a use case for `g.throw`, a ((("g.throw", id="gt4")))method that can be used to report errors that take place while the generator is suspended. Its applications become apparent when we think in terms of the flow control code driving the moments spent in between `yield` expressions, where things could go wrong. When something goes wrong processing an item in the sequence, the code that's consuming the generator needs to be able to `throw` that error into the generator. In the case of `modelProvider`, the iterator may experience network issues--or a malformed HTTP response--and fail to provide a piece of the model. In the following snippet of code, the `fetch` step was modified by adding an error callback that will be executed if parsing fails in `response.json()`, in which case we'll throw the exception at the generator function. [source,javascript] ---- fetch(`/model?query=${ encodeURIComponent(value) }`) .then(response => response.json()) .then(data => pull(data)) .catch(err => g.throw(err)) ---- When `g.next` is called, execution in generator code is unsuspended. The `g.throw` method also unsuspends the generator, but it causes an exception to be thrown at the location of the `yield` expression. An unhandled exception in a generator would stop iteration by preventing other `yield` expressions from being reachable. Generator code could wrap `yield` expressions in `try`/`catch` blocks to gracefully manage exceptions forwarded by iteration code--as shown in the following code snippet. This would allow subsequent `yield` expressions to be reached, suspending the generator and putting the iterator in charge once again. [source,javascript] ---- modelProvider(function* () { try { console.log('items in the cart:', yield 'cart.items') } catch (e) { console.error('uh oh, failed to fetch model.cart.items!', e) } try { console.log(`these are our products: ${ yield 'products' }`) } catch (e) { console.error('uh oh, failed to fetch model.products!', e) } }) ---- Even though generator functions allow us to suspend execution and then resume asynchronously, we can use the same error handling semantics—++try++, `catch`, and ++throw++—as with regular functions. Having the ability to use `try`/`catch` blocks in generator code lets us treat the code as if it were synchronous, even when there are HTTP requests sitting behind `yield` expressions, ((("g.throw", startref="gt4")))((("yield", startref="y4x")))((("generators", "throwing errors at", startref="gen4tea")))in iterator code. ==== Returning on Behalf of a Generator Besides `g.next` and `g.throw`, ((("generators", "returns in", id="gen4ri")))generator objects have one more method at their disposal to determine how a generator sequence is iterated: `g.return(value)`. ((("g.return(value)", id="grv4")))This method unsuspends the generator function and executes `return value` at the location of `yield`, typically ending the sequence being iterated by the generator object. This is no different to what would occur if the generator function actually had a `return` statement in it. [source,javascript] ---- function* numbers() { yield 1 yield 2 yield 3 } const g = numbers() console.log(g.next()) // <- { done: false, value: 1 } console.log(g.return()) // <- { done: true } console.log(g.next()) // <- { done: true } ---- Given that `g.return(value)` performs `return value` at the location of `yield` where the generator function was last suspended, a `try`/`finally` block could avoid immediate termination of the generated sequence, as statements in the `finally` block would be executed right before exiting. As shown in the following piece of code, that means `yield` expressions within the `finally` block can continue producing items for the sequence. [source,javascript] ---- function* numbers() { try { yield 1 } finally { yield 2 yield 3 } yield 4 yield 5 } const g = numbers() console.log(g.next()) // <- { done: false, value: 1 } console.log(g.return(-1)) // <- { done: false, value: 2 } console.log(g.next()) // <- { done: false, value: 3 } console.log(g.next()) // <- { done: true, value -1 } ---- Let's now look at a simple generator function, where a few values are yielded and then a `return` statement is encountered. [source,javascript] ---- function* numbers() { yield 1 yield 2 return 3 yield 4 } ---- While you may place `return value` statements anywhere in a generator function, the returned `value` won't show up when iterating the generator using the spread operator or `Array.from` to build an array, nor when using `for..of`, as shown next. [source,javascript] ---- console.log([...numbers()]) // <- [1, 2] console.log(Array.from(numbers())) // <- [1, 2] for (const number of numbers()) { console.log(number) // <- 1 // <- 2 } ---- This happens because the iterator result provided by executing `g.return` or a `return` statement contains the `done: true` signal, indicating that the sequence has ended. Even though that same iterator result also contains a sequence `value`, none of the previously shown methods take it into account when pulling a sequence from the generator. In this sense, `return` statements in generators should mostly be used as circuit-breakers and not as a way of providing the last value in a sequence. The only way of actually accessing the `value` returned from a generator is to iterate over it using a generator object, and capturing the iterator result `value` even though `done: true` is present, as displayed in the following snippet. [source,javascript] ---- const g = numbers() console.log(g.next()) // <- { done: false, value: 1 } console.log(g.next()) // <- { done: false, value: 2 } console.log(g.next()) // <- { done: true, value: 3 } console.log(g.next()) // <- { done: true } ---- Due to the confusing nature of the differences between `yield` expressions and `return` statements, `return` in generators would be best avoided except in cases where a specific method wants to treat `yield` and `return` differently, the end goal always being to provide an abstraction in exchange for a simplified development experience. In the following section, we'll build an iterator that leverages differences in `yield` versus `return` to perform both input and output based on the same generator ((("generators", "returns in", startref="gen4ri")))((("g.return(value)", startref="grv4")))function. ==== Asynchronous I/O Using Generators The ((("generators", "and asynchronous input/output", secondary-sortas="asynchronous", id="gen4aaio")))((("asynchronous functions", "with generators", secondary-sortas="generators", id="ac4wg")))following piece of code shows a self-describing generator function where we indicate input sources and an output destination. This hypothetical method could be used to pull product information from the yielded endpoints, which could then be saved to the returned endpoint. An interesting aspect of this interface is that as a user you don't have to spend any time figuring out how to read and write information. You merely determine the sources and destination, and the underlying implementation figures out the rest. [source,javascript] ---- saveProducts(function* () { yield '/products/modern-javascript' yield '/products/mastering-modular-javascript' return '/wishlists/books' }) ---- As a bonus, we'll have `saveProducts` return a promise that's fulfilled after the order is pushed to the returned endpoint, meaning the consumer will be able to execute callbacks after the order is filed. The generator function should also receive product data via the `yield` expressions, which can be passed into it by calling `g.next` with the associated product data. [source,javascript] ---- saveProducts(function* () { yield '/products/modern-javascript' yield '/products/mastering-modular-javascript' return '/wishlists/books' }).then(response => { // continue after storing the product list }) ---- Conditional logic could be used to allow `saveProducts` to target a user's shopping cart instead of one of their wish lists. [source,javascript] ---- saveProducts(function* () { yield '/products/modern-javascript' yield '/products/mastering-modular-javascript' if (addToCart) { return '/cart' } return '/wishlists/books' }) ---- One of the benefits of taking this blanket "inputs and output" approach is that the implementation could be changed in a variety of ways, while keeping the API largely unchanged. The input resources could be pulled via HTTP requests or from a temporary cache, they could be pulled one by one or concurrently, or there could be a mechanism that combines all yielded resources into a single HTTP request. Other than semantic differences of pulling one value at a time versus pulling them all at the same time to combine them into a single request, the API would barely change in the face of significant changes to the implementation. We'll go over an implementation of `saveProducts` bit by bit. First off, the following piece of code shows how we could combine `fetch` and its promise-based API to make an HTTP request for a JSON document about the first yielded product. [source,javascript] ---- function saveProducts(productList) { const g = productList() const item = g.next() fetch(item.value) .then(res => res.json()) .then(product => {}) } ---- In order to pull product data in a concurrent series--asynchronously, but one at a time--we'll wrap the `fetch` call in a recursive function that gets invoked as we get responses about each product. Each step of the way we'll be fetching a product, calling `g.next` to unsuspend the generator function asking for the next yielded item in the sequence, and then calling `more` to fetch that item. [source,javascript] ---- function saveProducts(productList) { const g = productList() more(g.next()) function more(item) { if (item.done) { return } fetch(item.value) .then(res => res.json()) .then(product => { more(g.next(product)) }) } } ---- Thus far we're pulling all inputs and passing their details back to the generator via ++g.next(product)++—an item at a time. In order to leverage the `return` statement, we'll save the products in a temporary array and then `POST` the list onto the output endpoint present on the iterator `item` when the sequence is marked as having ended. [source,javascript] ---- function saveProducts(productList) { const products = [] const g = productList() more(g.next()) function more(item) { if (item.done) { save(item.value) } else { details(item.value) } } function details(endpoint) { fetch(endpoint) .then(res => res.json()) .then(product => { products.push(product) more(g.next(product)) }) } function save(endpoint) { fetch(endpoint, { method: 'POST', body: JSON.stringify({ products }) }) } } ---- At this point product descriptions are being pulled down, cached in the `products` array, forwarded to the generator body, and eventually saved in one fell swoop using the endpoint provided by the `return` statement. In our original API design we suggested we'd return a promise from `saveProducts` so that callbacks could be chained and executed after the `save` operation. As we mentioned earlier, `fetch` returns a promise. By adding `return` statements all the way through our function calls, you can observe how `saveProducts` returns the output of `more`, which returns the output of `save` or `details`, both of which return the promise created by a `fetch` call. In addition, each `details` call returns the result of calling `more` from inside the `details` promise, meaning the original `fetch` won't be fulfilled until the second `fetch` is fulfilled, allowing us to chain these promises, which will ultimately resolve when the `save` call is executed and resolved. [source,javascript] ---- function saveProducts(productList) { const products = [] const g = productList() return more(g.next()) function more(item) { if (item.done) { return save(item.value) } return details(item.value) } function details(endpoint) { return fetch(endpoint) .then(res => res.json()) .then(product => { products.push(product) return more(g.next(product)) }) } function save(endpoint) { return fetch(endpoint, { method: 'POST', body: JSON.stringify({ products }) }) .then(res => res.json()) } } ---- As you may have noticed, the implementation doesn't hardcode any important aspects of the operation, which means you could use the inputs and output pattern in a generic way as long as you have zero or more inputs you want to pipe into one output. The consumer ends up with an elegant-looking method that's easy to understand--they `yield` input stores and `return` an output store. Furthermore, our use of promises makes it easy to concatenate this operation with others. This way, we're keeping a potential tangle of conditional statements and flow control mechanisms in check, by abstracting away flow control into the iteration mechanism under the `saveProducts` method. We've looked into flow control mechanisms such as callbacks, events, promises, iterators, and generators. The following two sections delve into `async`/`await`, async iterators, and async generators, all of which build upon a mixture of the flow control mechanisms we've uncovered thus far in this ((("generators", startref="gen4")))((("generators", "and asynchronous input/output", secondary-sortas="asynchronous", startref="gen4aaio")))((("asynchronous functions", "with generators", secondary-sortas="generators", startref="ac4wg")))chapter. === Async Functions Languages ((("asynchronous functions", id="af4")))like Python and C# have had `async`/`await` for a while. In ES2017, JavaScript gained native syntax that can be used to describe asynchronous operations. Let's go over a quick recap comparing promises, callbacks, and generators. Afterwards we'll look into async functions in JavaScript, and how this new feature can help make our code more readable. ==== Flavors of Async Code Let's suppose ((("asynchronous functions", "async code types", id="af4act")))we had code like the following. Here I'm wrapping a `fetch` request in a `getRandomArticle` ((("getRandomArticle", id="gra4")))function. The promise fulfills with the JSON `body` when successful, and follows standard `fetch` rejection mechanics otherwise. [source,javascript] ---- function getRandomArticle() { return fetch('/articles/random', { headers: new Headers({ Accept: 'application/json' }) }) .then(res => res.json()) } ---- The ((("chained promises")))((("promises", "chaining")))next piece of code shows how typical usage for `getRandomArticle` might look like. We build a promise chain that takes the JSON object for the article and passes it through an asynchronous `renderView` view rendering function, which fulfills as an HTML page. We then replace the contents of our page with that HTML. In order to avoid silent errors, we'll also print any rejection reasons using `console.error`. [source,javascript] ---- getRandomArticle() .then(model => renderView(model)) .then(html => setPageContents(html)) .then(() => console.log('Successfully changed page!')) .catch(err => console.error(err)) ---- Chained promises can become hard to debug: the root cause of a flow control error can be challenging to track down, and writing promise-based code flows is typically much easier than reading them, which leads to code that becomes difficult to maintain over time. If we were to use plain JavaScript ((("callbacks")))callbacks, our code would become repetitive, as demonstrated in the next code listing. At the same time, we're running into callback hell: we're adding a level of indentation for each step in our asynchronous code flow, making our code increasingly harder to read with each step we add. [source,javascript] ---- getRandomArticle((err, model) => { if (err) { return console.error(err) } renderView(model, (err, html) => { if (err) { return console.error(err) } setPageContents(html, err => { if (err) { return console.error(err) } console.log('Successfully changed page!') }) }) }) ---- Libraries can, of course, help with callback hell and repetitive error handling. Libraries like `async` take advantage of normalized callbacks where the first argument is reserved for errors. Using their `waterfall` method, our code becomes terse again. [source,javascript] ---- async.waterfall([ getRandomArticle, renderView, setPageContents ], (err, html) => { if (err) { return console.error(err) } console.log('Successfully changed page!') }) ---- Let's look at a similar example, but this time we'll be using generators. The following is a rewrite of `getRandomArticle` where we consume a generator for the sole purpose of changing the way in which `getRandomArticle` is consumed. [source,javascript] ---- function getRandomArticle(gen) { const g = gen() g.next() // advance the generator to the first yield expression fetch('/articles/random', { headers: new Headers({ Accept: 'application/json' }) }) .then(res => res.json()) .then(json => g.next(json)) .catch(err => g.throw(err)) } ---- The following piece of code shows how you can pull the `json` from `getRandomArticle` by way of a `yield` expression. Even though that looks somewhat synchronous, there's now a generator function wrapper involved. As soon as we want to add more steps, we need to heavily modify `getRandomArticle` so that it yields the results we want, and make the necessary changes to the generator function in order to consume the updated sequence of ((("getRandomArticle", startref="gra4")))results. [source,javascript] ---- getRandomArticle(function* printRandomArticle() { const json = yield // render view }) ---- Generators may not be the most straightforward way of accomplishing the results that we want in this case: you're only moving the complexity somewhere else. We might as well stick with promises. Besides involving an unintuitive syntax into the mix, your iterator code will be highly coupled to the generator function that's being consumed. That means you'll have to change it often as you add new `yield` expressions to the generator code. A better alternative would be to use an ((("asynchronous functions", "async code types", startref="af4act")))async function. ==== Using async/await Async functions ((("asynchronous functions", "async/await", id="af4a")))let us take a promise-based implementation and take advantage of the synchronous-looking generator style. A huge benefit in this approach is that you won't have to change the original `getRandomArticle` at all: as long as it returns a promise it can be awaited. Note that `await` may only be used inside async functions, marked with the `async` keyword. Async functions work similarly to generators, by suspending execution in the local context until a promise settles. If the awaited expression isn't originally a promise, it gets casted into a promise. The following piece of code consumes our original `getRandomArticle`, which relied on promises. Then it runs that model through an asynchronous `renderView` function, which returns a bit of HTML, and updates the page. Note how we can use `try`/`catch` to handle errors in awaited promises from within the `async` function, treating completely asynchronous code as if it were synchronous. [source,javascript] ---- async function read() { try { const model = await getRandomArticle() const html = await renderView(model) await setPageContents(html) console.log('Successfully changed page!') } catch (err) { console.error(err) } } read() ---- An async function always returns a `Promise`. In the case of uncaught exceptions, the returned promise settles in rejection. Otherwise, the returned promise resolves to the return value. This aspect of async functions allows us to mix them with regular promise-based continuation as well. The following example shows how the two may be combined. [source,javascript] ---- async function read() { const model = await getRandomArticle() const html = await renderView(model) await setPageContents(html) return 'Successfully changed page!' } read() .then(message => console.log(message)) .catch(err => console.error(err)) ---- Making the `read` function a bit more reusable, we could return the resulting `html`, and allow consumers to do continuation using promises or yet another async function. That way, your `read` function becomes only concerned with pulling down the HTML for a view. [source,javascript] ---- async function read() { const model = await getRandomArticle() const html = await renderView(model) return html } ---- Following the example, we can use plain promises to print the HTML. [source,javascript] ---- read().then(html => console.log(html)) ---- Using async functions wouldn't be all that difficult for continuation, either. In the next snippet, we create a `write` function used for continuation. [source,javascript] ---- async function write() { const html = await read() console.log(html) } ---- What about concurrent asynchronous flows? ==== Concurrent Async Flows In ((("asynchronous functions", "concurrent async flows", id="af4caf")))asynchronous code flows, it is commonplace to execute two or more tasks concurrently. While async functions make it easier to write asynchronous code, they also lend themselves to code that executes one asynchronous operation at a time. A function with multiple `await` expressions in it will be suspended one at a time on each `await` expression until that `Promise` is settled, before unsuspending execution and moving onto the next `await` expression--this is a similar case to what we observe with generators and `yield`. [source,javascript] ---- async function concurrent() { const p1 = new Promise(resolve => setTimeout(resolve, 500, 'fast') ) const p2 = new Promise(resolve => setTimeout(resolve, 200, 'faster') ) const p3 = new Promise(resolve => setTimeout(resolve, 100, 'fastest') ) const r1 = await p1 // execution is blocked until p1 settles const r2 = await p2 const r3 = await p3 } ---- We can use `Promise.all` to ((("Promise.all")))work around that issue, creating a single promise that we can `await` on. This way, our code blocks until every promise in a list is settled, and they can be resolved concurrently. The following example shows how you could `await` on three different promises that could be resolved concurrently. Given that `await` suspends your `async` function and the `await Promise.all` expression ultimately resolves into a `results` array, you can take advantage of destructuring to pull individual results out of that array. [source,javascript] ---- async function concurrent() { const p1 = new Promise(resolve => setTimeout(resolve, 500, 'fast') ) const p2 = new Promise(resolve => setTimeout(resolve, 200, 'faster') ) const p3 = new Promise(resolve => setTimeout(resolve, 100, 'fastest') ) const [r1, r2, r3] = await Promise.all([p1, p2, p3]) console.log(r1, r2, r3) // 'fast', 'faster', 'fastest' } ---- We could use `Promise.race` to ((("Promise.race")))get the result from the promise that fulfills ((("asynchronous functions", "concurrent async flows", startref="af4caf")))quicker. [source,javascript] ---- async function race() { const p1 = new Promise(resolve => setTimeout(resolve, 500, 'fast')) const p2 = new Promise(resolve => setTimeout(resolve, 200, 'faster')) const p3 = new Promise(resolve => setTimeout(resolve, 100, 'fastest')) const result = await Promise.race([p1, p2, p3]) console.log(result) // 'fastest' } ---- ==== Error Handling Errors ((("asynchronous functions", "error handling")))are swallowed silently within an `async` function, just like inside normal Promises, due to async functions being wrapped in a `Promise`. Uncaught exceptions raised in the body of your async function or during suspended execution while evaluating an `await` expression will reject the promise returned by the `async` function. That is, unless we ((("try/catch")))((("catch")))add `try`/`catch` blocks around `await` expressions. For the portion of the async function code that's wrapped, errors are treated under typical `try`/`catch` semantics. Naturally, this can be seen as a strength: you can leverage `try`/`catch` conventions, something you were unable to do with asynchronous callbacks, and somewhat able to when using promises. In this sense, async functions are akin to generators, where we can take advantage of `try`/`catch` thanks to function execution suspension turning asynchronous flows into seemingly synchronous code. Furthermore, you're able to catch these exceptions from outside the `async` function, by adding a `.catch` clause to the promise they return. While this is a flexible way of combining the `try`/`catch` error handling flavor with `.catch` clauses in promises, it can also lead to confusion and ultimately cause to errors going unhandled, unless everyone reading the code is comfortable with async function semantics in terms of the promise wrapper and how `try`/`catch` works under this context. [source,javascript] ---- read() .then(html => console.log(html)) .catch(err => console.error(err)) ---- As you can see, there are quite a few ways in which we can notice exceptions and then handle, log, or offload them. ==== Understanding Async Function Internals Async functions ((("asynchronous functions", "internals of", id="af4io")))leverage both generators and promises internally. Let's suppose we have the following async function. [source,javascript] ---- async function example(a, b, c) { // example function body } ---- The next bit shows how the `example` declaration could be converted into a plain old `function` that returns the result of feeding a generator function to a `spawn` ((("spawn", id="spawn4")))helper. [source,javascript] ---- function example(a, b, c) { return spawn(function* () { // example function body }) } ---- Inside the generator function, we'll assume `yield` to be the syntactic equivalent of `await`. In `spawn`, a promise is wrapped around code that will step through the generator function--made out of user code--in series, forwarding values to the generator code (the `async` function's body). The following listing should aid you in understanding how the `async`/`await` algorithm iterates over a sequence of `await` expressions using a generator. Each item in the sequence is wrapped in a promise and then gets chained with the next step in the sequence. The promise returned by the underlying generator function becomes settled when the sequence ends or one of the promises is rejected. [source,javascript] ---- function spawn(generator) { // wrap everything in a promise return new Promise((resolve, reject) => { const g = generator() // run the first step step(() => g.next()) function step(nextFn) { const next = runNext(nextFn) if (next.done) { // finished with success, resolve the promise resolve(next.value) return } // not finished, chain off the yielded promise // and run next step Promise .resolve(next.value) .then( value => step(() => g.next(value)), err => step(() => g.throw(err)) ) } function runNext(nextFn) { try { // resume the generator return nextFn() } catch (err) { // finished with failure, reject the promise reject(err) } } }) } ---- Consider the following async function. In order to print the result, we're also using promise-based continuation. Let's follow the code as a thought exercise. [source,javascript] ---- async function exercise() { const r1 = await new Promise(resolve => setTimeout(resolve, 500, 'slowest') ) const r2 = await new Promise(resolve => setTimeout(resolve, 200, 'slow') ) return [r1, r2] } exercise().then(result => console.log(result)) // <- ['slowest', 'slow'] ---- First, we could translate the function to our ++spawn++–based logic. We wrap the body of our async function in a generator passed to `spawn`, and replace any `await` expressions with `yield`. [source,javascript] ---- function exercise() { return spawn(function* () { const r1 = yield new Promise(resolve => setTimeout(resolve, 500, 'slowest') ) const r2 = yield new Promise(resolve => setTimeout(resolve, 200, 'slow') ) return [r1, r2] }) } exercise().then(result => console.log(result)) // <- ['slowest', 'slow'] ---- When `spawn` is called with the generator function, it immediately creates a generator object and executes `step` a first time, as seen in the next code snippet. The `step` function will also be used whenever we reach a `yield` expression; those are equivalent to the `await` expressions in our async function. [source,javascript] ---- function spawn(generator) { // wrap everything in a promise return new Promise((resolve, reject) => { const g = generator() // run the first step step(() => g.next()) // … }) } ---- The first thing that ((("spawn", startref="spawn4")))happens in the `step` function is calling the `nextFn` function inside a `try`/`catch` block. This resumes execution in the generator function. If the generator function were to produce an error, we'd fall into the `catch` clause, and the underlying promise for our async function would be rejected without any further steps, as shown next. [source,javascript] ---- function step(nextFn) { const next = runNext(nextFx) // … } function runNext(nextFn) { try { // resume the generator return nextFn() } catch (err) { // finished with failure, reject the promise reject(err) } } ---- Back to the async function, code up until the following expression is evaluated. No errors are incurred, and execution in the async function is suspended once again. [source,javascript] ---- yield new Promise(resolve => setTimeout(resolve, 500, 'slowest') ) ---- The yielded expression is received by `step` as `next.value`, while `next.done` indicates whether the generator sequence has ended. In this case, we receive the `Promise` in the function controlling exactly how iteration should occur. At this time, `next.done` is `false`, meaning we won't be resolving the async function's wrapper `Promise`. We wrap `next.value` in a fulfilled `Promise`, just in case we haven't received a `Promise`. We then wait on the `Promise` to be fulfilled or rejected. If the promise is fulfilled, we push the fulfillment value to the generator function by advancing the generator sequence with `value`. If the promise is rejected, we would've used `g.throw`, which would've resulted in an error being raised in the generator function, causing the async function's wrapper promise to be rejected at `runNext`. [source,javascript] ---- function step(nextFn) { const next = runNext(nextFn) if (next.done) { // finished with success, resolve the promise resolve(next.value) return } // not finished // chain off the yielded promise and run next step Promise .resolve(next.value) .then( value => step(() => g.next(value)), err => step(() => g.throw(err)) ) } ---- Using `g.next()` on its own means that the generator function resumes execution. By passing a value to `g.next(value)`, we've made it so that the `yield` expression evaluates to that `value`. The `value` in question is, in this case, the fulfillment value of the originally yielded `Promise`, which is `'slowest'`. Back in the generator function, we assign `'slowest'` to `r1`. [source,javascript] ---- const r1 = yield new Promise(resolve => setTimeout(resolve, 500, 'slowest') ) ---- Then, execution runs up until the second `yield` statement. The `yield` expression once again causes execution in the async function to be suspended, and sends the new `Promise` to the `spawn` iterator. [source,javascript] ---- yield new Promise(resolve => setTimeout(resolve, 200, 'slow')) ---- The same process is repeated this time: `next.done` is `false` because we haven't reached the end of the generator function. We wrap the `Promise` in another promise just in case, and once the promise settles with `'slow'`, we resume execution in the generator function. Then we reach the return statement in the generator function. Once again, execution is suspended in the generator function, and returned to the iterator. [source,javascript] ---- return [r1, r2] ---- At this point, `next` evaluates to the following object. ---- { value: ['slowest', 'slow'], done: true } ---- Immediately, the iterator checks that `next.done` is indeed `true`, and resolves the async function to `['slowest', 'slow']`. [source,javascript] ---- if (next.done) { // finished with success, resolve the promise resolve(next.value) return } ---- Now that the promise returned by `exercise` is settled in fulfillment, the log statement is finally printed. [source,javascript] ---- exercise().then(result => console.log(result)) // <- ['slowest', 'slow'] ---- Async functions, then, are little more than a sensible default when it comes to iterating generator functions in such a way that makes passing values back and forth as frictionless as possible. Some syntactic sugar hides away the generator function, the `spawn` function used to iterate over the sequence of yielded expressions, and `yield` becomes `await`. Another way of thinking of async functions is in terms of promises. Consider the following example, where we have an async function that awaits for a promise that's the result of a function call, and then awaits on mapping every user through a function. How would you translate it, in terms of promises? [source,javascript] ---- async function getUserProfiles() { const users = await findAllUsers() const models = await Promise.all(users.map(toUserModel)) const profiles = models.map(model => model.profile) return profiles } ---- The following snippet has a rough equivalent of the `getUserProfiles` async function. Note how, for the most part, we can change `await` statements into chained promises while moving variable declarations in the async function into reactions on each of those promises, as needed. Given async functions always return a `Promise`, we leave that unchanged in this case, but we'd have to remember to mentally assign a return value of `Promise.resolve(result)` to any async function we want to translate into promises in our heads. [source,javascript] ---- function getUserProfiles() { const userPromise = findAllUsers() const modelPromise = userPromise.then(users => Promise.all(users.map(toUserModel)) ) const profilePromise = modelPromise.then(models => models.map(model => model.profile) ) return profilePromise } ---- Noting that async functions are ((("syntactic sugar")))syntactic sugar on top of generators and promises, we can also make a point about the importance of learning how each of these constructs work in order to get better insight into how you can mix, match, and combine all the different flavors of asynchronous code flows ((("asynchronous functions", startref="af4")))((("asynchronous functions", "internals of", startref="af4io")))((("asynchronous functions", "async/await", startref="af4a")))together. === Asynchronous Iteration As ((("asynchronous iteration", id="ai4")))((("iteration protocol", "asychronous iterators", see="asynchronous iterators")))explained in <>, you may recall how iterators leverage `Symbol.iterator` as ((("Symbol.iterator")))an interface to define how an object is to be iterated. [source,javascript] ---- const sequence = { [Symbol.iterator]() { const items = ['i', 't', 'e', 'r', 'a', 'b', 'l', 'e'] return { next: () => ({ done: items.length === 0, value: items.shift() }) } } } ---- You may also recall that the `sequence` object ((("sequence")))can be iterated in a number of different ways, such as the spread operator, `Array.from`, ((("Array.from")))and `for..of`, among others. [source,javascript] ---- [...sequence] // <- ['i', 't', 'e', 'r', 'a', 'b', 'l', 'e'] Array.from(sequence) // <- ['i', 't', 'e', 'r', 'a', 'b', 'l', 'e'] for (const item of sequence) { console.log(item) // <- 'i' // <- 't' // <- 'e' // <- 'r' // <- 'a' // <- 'b' // <- 'l' // <- 'e' } ---- The contract for an iterator mandates that the `next` ((("next")))method of `Symbol.iterator` instances returns an object with `value` and `done` properties. The `value` ((("value")))property indicates the current value in the sequence, while `done` ((("done")))is a Boolean indicating whether the sequence has ended. ==== Async Iterators In ((("asynchronous iteration", "async iterators", id="ai4ai")))async iterators, the contract has a subtle difference: `next` is supposed to return a `Promise` that resolves to an object containing `value` and `done` properties. The promise enables the sequence to define asynchronous tasks before the next item in the series is resolved. A new `Symbol.asyncIterator` is ((("Symbol.asyncIterator")))introduced to declare asynchronous iterators, in order to avoid confusion that would result from reusing `Symbol.iterator`. The `sequence` iterable could be made compatible with the async iterator interface with two small changes: we replace `Symbol.iterator` with `Symbol.asyncIterator`, and we wrap the return value for the `next` method in `Promise.resolve`, thus returning a `Promise`. [source,javascript] ---- const sequence = { [Symbol.asyncIterator]() { const items = ['i', 't', 'e', 'r', 'a', 'b', 'l', 'e'] return { next: () => Promise.resolve({ done: items.length === 0, value: items.shift() }) } } } ---- A case could be made for an infinite sequence that increases its value at a certain time interval. The following example has an `interval` ((("interval")))function that returns an infinite async sequence. Each step resolves to the next value in the sequence after `duration`. [source,javascript] ---- const interval = duration => ({ [Symbol.asyncIterator]: () => ({ i: 0, next() { return new Promise(resolve => setTimeout(() => resolve({ value: this.i++, done: false }), duration) ) } }) }) ---- In order to consume an async iterator, we can leverage the new `for await..of` construct introduced alongside async iterators. This is yet another way of writing code that behaves asynchronously yet looks synchronous. Note that `for await..of` statements ((("for await..of")))are only allowed inside async functions. [source,javascript] ---- async function print() { for await (const i of interval(1000)) { console.log(`${ i } seconds elapsed.`) } } print() ---- Note that async iterators--as well as async generators--are in stage 3 of the ECMAScript process as of the time of this ((("asynchronous iteration", "async iterators", startref="ai4ai")))writing. ==== Async Generators Like ((("asynchronous iteration", "async generators")))((("generators", "async")))with regular iterators, there are async generators to complement async iterators. An async generator function is like a generator function, except that it also supports `await` and `for await..of` declarations. The following example shows a `fetchInterval` generator that fetches a resource periodically at an interval. [source,javascript] ---- async function* fetchInterval(duration, ...params) { for await (const i of interval(duration)) { yield await fetch(...params) } } ---- When stepped over, async generators return objects with a `{ next, return, throw }` signature, whose methods return promises for `{ value, done }`. This is in contrast with regular generators, which return `{ value, done }` directly. You can consume the `fetchInterval` async generator in exactly the same way you could consume the object-based `interval` async iterator. The following example consumes the `fetchInterval` generator to poll an `/api/status` HTTP resource and leverage its JSON response. After each step ends, we wait for a second and repeat the process. [source,javascript] ---- async function process() { for await (const response of fetchInterval( 1000, '/api/status' )) { const data = await response.json() // use updated data } } process() ---- As highlighted in <>, it's important to break out of these kinds of sequences, in order to avoid infinite loops.