© Russ Ferguson and Keith Cirkel 2017
Russ Ferguson and Keith CirkelJavaScript Recipes10.1007/978-1-4302-6107-0_21

21. Working with Asynchronous Control Flow and Promises

Russ Ferguson and Keith Cirkel2
(1)
Ocean, New Jersey, USA
(2)
London, UK
 

What Is Asynchronous Control Flow?

Caution
Let variables and arrow functions are ES6 features. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support these features. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You want a better understanding of asynchronous control flow.

Solution

Asynchronous control flow is the name of a set of patterns to help control the order of execution of asynchronous code. To manage a programs flow synchronously, you would use if statements and try/catch statements. These work fine for synchronous code because each statement executes and the return value is evaluated before continuing to the next statement. Asynchronous code, however, is code that eventually will fulfill but some (indeterminate) time in the future and so cannot be predictably relied upon to succeed by the next statement or stack frame. To allow for code to be asynchronous, some languages offer the ability to create new threads, which run independently but can communicate with each other. JavaScript’s concurrency model is based on an event loop. The event loop effectively iterates over a task queue, executing each task until there are no tasks left. Tasks can execute small pieces of synchronous code, and subsequently defer the next stage of execution until a new task in the near future.
Two of the biggest paradigms for using callbacks in JavaScript are events and promises. Node.js also heavily utilizes the callback pattern throughout the core library, but does not use promises. Promises (and Node.js style callbacks) are very useful for single-result functions, for example for fs.writeFile, which fires the callback when all of the given contents have been written to the file. Events, which are described in Chapter 18, are used both in the browser and Node.js heavily and are very useful for asynchronous functionality that is fired potentially multiple times—for example, with element.addEventListener('click'), which could fire once or thousands of times during its life. Promises are a recent addition to the language, officially built into ECMAScript 6 and existing for a few years prior through external libraries. Promises are essentially task queues that “promise” a value will at some point be returned from asynchronous code. This chapter covers promises in depth.

The Code

// Below is how Asynchronous Control Flow can be achieved with the Node.js Callback Pattern. This example uses fs.writeFile to write a file to the filesystem
let file = fs.writeFile('data.txt', data, (err) => {
    if (err) { console.log('Oh no, an error!'); }
    else  console.log('file has been written!');
});
// Below is how Asynchronous Control Flow works with Event Emitters, for example Sockets in node.js
let socket = new Socket(), data;
socket.on('data', chunk => data += chunk);
socket.on('end', () => console.log('all data has been sent!'));
socket.on('error', (err) => console.log('Oh no, an error!', err));
// Here is how the Event Emitter pattern works in browsers, in the DOM using XMLHTTPRequest
let xhr = new XMLHTTPRequest();
xhr.addEventListener('load', () => console.log('all data has been sent'));
xhr.addEventListener('error', () => console.log('Oh no, an error!'));
xhr.open();
// Here is how the Promise pattern works, for example making a request using a Request Promise library
let req = request('http://google.com')
    .then(() => console.log('Request has finished'))
    .catch((err) => console.log('Oh no, an error!'));
Listing 21-1.
What Is Asynchronous Control Flow?

How It Works

First, it is important to understand how asynchronous code works. Essentially, an interpreter given a block of code will execute each statement one directly after the other. While this happens, the interpreter will not be able to do anything else (in a single thread). It will use as much CPU as it can to execute each statement, and when all statements are done it will finish and terminate the program. The problem herein lies that some functions, especially ones that deal with I/O will take a variable amount of time, compared to CPU bound functions, which take a (reasonably) predictable amount of CPU time. Traditional languages that are single threaded and I/O blocking will stop everything and simply wait for the indeterminately timed function to finish, and then proceed with the remainder of the code. Consider Figure 21-1, which illustrates a set of synchronous tasks done in a language that blocks for I/O.
A315054_1_En_21_Fig1_HTML.jpg
Figure 21-1.
Timeline of a synchronous program
There are many languages available that exhibit this non-asynchronous behavior; they are “I/O blocking,” for example PHP. PHP is a single threaded (cannot talk to other CPU threads in an asynchronous manner) interpreted language, just like JavaScript is, but because PHP’s I/O is blocking by nature, every time the thread does an I/O operation such as a network request or write to disk, it must wait until that i/o completes (so it can evaluate the return value) before moving onto the next line of code. each new function call is a stack frame and is added onto the pile of stack frames until they all complete and the program ends.
JavaScript mitigates this behavior with the event loop (not to be confused with events in JavaScript). It offers the same behavior with regards to stack frames, in that synchronous code will pile up in stack frames and all be executed as fast as possible. JavaScript’s event loop adds an additional benefit through the task queue and tasks, which get executed in order but after the existing set of stack frames have been executed. In the language, this presents itself as functions like setTimeout(), setInterval(), setImmediate(), and in the case of Node.js process.nextTick(). Through these and other mechanisms, JavaScript uses the callback pattern, where functions can be passed as arguments, which can be executed in the near future, in a new task. This method of deferring functionality effectively makes up a simple concurrency model (i.e., a way to handle asynchronous code) for JavaScript. The event loop in itself does not make JavaScript non-blocking, but provides a layer of asynchronous concurrency, which most APIs (such as the DOM and Node.js) take advantage of .
By using this concurrency model to queue up the completion of I/O operations, JavaScript can execute all of the synchronous code setup first, then idle while waiting for the asynchronous code to complete, and finally fire all of the event handlers, callbacks, or promises that finish the program’s execution. This means I/O-based code will appear to run simultaneously, and much faster than blocking I/O-based code. Consider Figure 21-2 and compare it to Figure 21-1 to see the difference in these paradigms.
A315054_1_En_21_Fig2_HTML.jpg
Figure 21-2.
Timeline of an asynchronous program

What Is a Promise?

Caution
Promises are an ES6 feature. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support this feature. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You want to know what a promise is and how it works.

Solution

Promises are the standard in ECMAScript for dealing with asynchronous task queues. There were many implementations prior to their ratification in the ES6 specification, such as Q, RSVP.js, and Bluebird, but the canonical implementation that features in JavaScript Implementations is the one we will be working with in this chapter.

How It Works

Promises are essentially a list of functions that get built up programmatically and will be fired one by one when the promise queue starts. A promise is a concept of a type of data that promises to one day be fulfilled (i.e., it is nothing yet, but will soon be something). Promises exist in three states—pending (or “paused”), where the promise queue (function list) is queued up and none are being executed, fulfilled (or “playing”), where callbacks from the promise queue are being fired one by one, and any new tasks are added to the promise queue to be fired very soon, or rejected (or “failing”), where the promise queue is being fired but using the error state functions (explained later in the chapter).
When the promise is created, it is given the ability to resolve or reject, but starts in the pending state. The program has to manually resolve or reject the promise, which will put it into the fulfilled or rejected state respectively. When a promise has been resolved or rejected, it cannot be pending ever again, but it may switch between fulfilled or rejected during its lifetime.
Promises are guaranteed to be asynchronous—each new function added to the promise’s queue is fired on a new task in the event loop (the queue of JavaScript’s underlying concurrency), and so a promise’s queue is a reasonably close parallel to the event loop’s task queue.
See Figure 21-3 for a basic example of a promise. The Promise is created with five callbacks (essentially functions). The promise starts in a pending state, where tasks are added (the blocks marked as “then”) until resolve() is called. It sets the promise to a fulfilled state, and so for every iteration of the event loop task queue , one by one, each callback in the promise queue (the “then”s) is called and completed. Skip ahead to Figures 21-5 and 21-6 to see an in-depth overview of how this sort of promise can be constructed.
A315054_1_En_21_Fig3_HTML.jpg
Figure 21-3.
Timeline of a basic promise
One of the interesting parts about promises is that when the state is moved away from pending, it cannot go back into a pending state. Because of this, callbacks (the “then” blocks in the illustrations—each one represents a callback) can be added retrospectively to a promise and will simply execute on the next iteration of the event loop. To put this another way, promises exist for the lifetime of the application (unless they are freed from memory) and will perpetually execute anything in their promise’s queue. This means, as a developer, you don’t necessarily need to care about the current state of a promise. It will still eventually execute the callbacks you’re adding, regardless of whether it has been fulfilled (actually, a promise could remain pending for the lifetime of the code, but this is rare and usually a result of a bug). Figure 21-4 illustrates how this could potentially work, given a similar example as in Figure 21-3.
A315054_1_En_21_Fig4_HTML.jpg
Figure 21-4.
Timeline of a promise having tasks added to it past resolution
This feature of a promise continuing to resolve its queue beyond the point of fulfillment reveals an interesting coincidental feature—a promise can be fulfilled before it has anything in its queue. The callbacks can be added post-fulfillment. Refer to Figure 21-5 for an example of how this might work.
A315054_1_En_21_Fig5_HTML.jpg
Figure 21-5.
Timeline of a promise having tasks added to it past resolution
Typical synchronous control flow not only includes conditional code paths with if/else but also allows trapping of errors with try/catch. With promises this can also be done using a similar mechanism. Promises can, of course, also be put into the rejected state. When a promise is in the rejected state it will not execute any resolvedActions (then()); instead, it executes the rejectedActions (catch()). When executing any rejectedActions, the promise can be switched back into a fulfilled state (switching from a rejected state) if the rejectedAction returns a value rather than throwing a failure. Consider Figure 21-6, which describes how this process works .
A315054_1_En_21_Fig6_HTML.jpg
Figure 21-6.
Timeline of a promise being rejected and caught with catch
Of course, a rejectedAction doesn’t necessarily always have to put a promise back into a fulfilled state; it can pass the error back into the promise chain, causing the promise to remain in the rejected state, and skipping the next resolvedAction and instead running the next rejectedAction. Similarly, resolvedActions can throw errors to put an already fulfilled promise into a rejected state. Figure 21-7 shows how this might work .
A315054_1_En_21_Fig7_HTML.jpg
Figure 21-7.
Timeline of a promise being resolved and rejected by a then
These then()/catch() features closely mimic synchronous code’s try/catch, and they also work to cover some use cases of if/else. You are encouraged in your promises to flip the state of a promise between fulfilled and rejected as you see fit, to help handle what could be considered the “happy path” and the “unhappy path.” Refer to Figures 21-8 and 21-9 for more interesting examples of how this then()/catch() behavior can work.
A315054_1_En_21_Fig8_HTML.jpg
Figure 21-8.
Timeline of a promise being resolved, rejected, and re-resolved
A315054_1_En_21_Fig9_HTML.jpg
Figure 21-9.
Timeline of a promise being caught and “rethrown ”
Catching, resolving, and “rethrowing” are all paradigms of synchronous code, mapped into promises. They can create complex asynchronous workflows. They also become more powerful than try/catch when you consider that you can defer dealing with any errors until the last rejectedAction in your promise, meaning you don’t need to sprinkle rejectedAction callbacks everywhere in your code. Promises can become even more complex when you consider the other powerful feature: Promises can be composed together. The return values of either resolvedActions or rejectedActions can also be a promise, and execution and state is deferred to that promise until it has completed all of its queue, to which it hands back the flow of execution to the originating promise. This can become incredibly complex; refer to Figure 21-10 for a simplified illustration of nested promises.
A315054_1_En_21_Fig10_HTML.jpg
Figure 21-10.
Timeline of a simple set of nested promises
In Figure 21-10, the promise is fulfilled, but the first resolvedAction (then()) returns a promise. The promise is created, and therefore executed, on the resulting call of the resolvedAction, so it resolves further down the line. During its fulfillment, it needs to complete all of its queue (resolvedActions and rejectedActions) before the originating promise can continue to process its queue. This composition ability makes promises incredibly powerful for handling sequential asynchronous tasks. As mentioned, however, the state of the nested promise determines the state going upstream, so while Figure 21-10 shows the nested promise successfully resolving, Figure 21-11 shows what would happen if the nested promise transforms into a rejected state. In Figure 21-11, you can see that the nested promise is transformed into a rejected state, and subsequently transforms the upstream promise to a rejected state, causing it to skip any subsequent resolvedActions and move to the nearest rejectedAction. This can provide some really interesting control flow, and is especially useful for tasks where you don’t mind about the state of a nested promise. You simply catch the error properly in the upstream promise and return it to its rightful state.
A315054_1_En_21_Fig11_HTML.jpg
Figure 21-11.
Timeline of a set of nested promises, where the child promise becomes rejected
All of these features of promises make them incredibly powerful, but also incredibly complex. Understanding these concepts in their discrete form is key to grasping them. Ensure that you keep these concepts in mind to avoid underutilizing promises and potentially running into problems that could easily be solved by the many flexibilities and power of promises .

Creating a Promise

Caution
Promises, let variables, and arrow functions are ES6 features. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support these features. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You want to be able to create a promise.

Solution

The promise constructor function can be used to make new promise instances. It takes one argument, which is a function that it immediately invokes. It gives the function two arguments—the first is a function to call when you want to fulfill the promise—e.g., when the wrapped asynchronous function has completed. The second is a function to call when you want the reject the promise—e.g., when the wrapped asynchronous function has failed with an error. The promise constructor function will return a new instance of promise, which is used to then queue up tasks.

The Code

let readFilePromise = new Promise((resolve, reject) => {
  fs.readFile('data.txt', (err, data) => {
      if (err) {
          reject(err);
      } else {
          resolve(data);
      }
  });
});
let writeFilePromise = new Promise((resolve, reject) => {
  fs.writeFile('data.txt', data, err => {
      if (err) {
          reject(err);
      } else {
          resolve();
      }
  });
});
Listing 21-2.
Creating a Promise

How It Works

The promise constructor doesn’t actually invoke any asynchronous code, although the resulting callbacks are guaranteed to run on a subsequent iteration of the event loop, and so are asynchronous. The promise constructor simply invokes the given function with the resolve and reject functions, leaving it to the developer to wrap asynchronous commands and turn them into a promise, as shown in the examples.
The callback function is given the two arguments—resolve and reject—so that the callback can manage the desired state of the promise. For example, if you’re making a call to Node.js’ fs.writeFile(), you might call resolve() when the operation has completed, putting the promise into a fulfilled state, unless there is an error, wherein reject(error) would be called, putting the Promise into a rejected state. As discussed earlier in this chapter, this determines the execution flow of the promise, whether it moves to a resolvedAction or a rejectedAction.

Adding Tasks to the Promise Queue with then()

Caution
Promises, let variables, and arrow functions are ES6 features. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support these features. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You want to be able to add a callback to a promise that will be executed when the promise is resolved.

Solution

The Promise.prototype.then() function takes two arguments, both functions. The first function is the resolvedAction, the second is rejectedAction. Only one of these function arguments is ever fired; the other one is never fired. Which one is determined by the state of the promise. The first function argument (the resolvedAction argument) is what to do if the state of the promise at the previous point in the queue was rejected and defaults to a function that returns the first given argument. The second argument is described later in this chapter. resolvedAction will be called with the resolveResult. That is the value that was passed to the resolve() function in the original promise constructor, or the value returned from the last called resolveAction or rejectAction. Whatever the value that is returned from the resolvedAction function becomes the new resolveResult. If resolvedAction throws, then the thrown value will become the rejectResult and the promise will enter a rejected state.

The Code

let writeFilePromise = new Promise((resolve, reject) => {
  fs.writeFile('data.txt', data, err => {
      if (err) {
          reject(err);
      } else {
          resolve();
      }
  });
});
writeFilePromise.then(() => console.log('file has been written!'));
Listing 21-3.
Adding Tasks to the Promise Queue with then()
The output is:
// When data.txt writes successfully
file has been written!
// When data.txt threw an error during write
[error thrown]

How It Works

Promise.prototype.then() allows you to add callbacks to any promise instance. The given callbacks are queued up and executed depending on the outcome state of the last callback. The functions passed to it work similarly to the promise constructor, in that the function calls themselves are not asynchronous and should not execute any asynchronous code. These callbacks differ from the promise constructor, in that they have no mechanism for deferring their return value, other than returning a new promise (which does). In other words, both of the functions you pass to it should use synchronous code or return promises that invoke asynchronous code.
then() can be called on a promise in any state. When the promise is in a pending state, it will wait until it is in a fulfilled or rejected before it executes any arguments in a then() function. If it is in the fulfilled state, the queue is executed, one by one, calling each of the resolvedActions (the first argument in a then() call). A promise that is already fulfilled will call the next resolvedAction in the queue. Remember though that a promise can change state to rejected at any time, meaning that, while you can guarantee one of the functions given to then() will be called, you can never guarantee which one it will be. A best practice is to always ensure you manage the outcome of both states at every reasonable opportunity, or at the very least always attempt to put a rejectedAction at the end of the promise chain. See Figure 21-12 illustrates the behaviors of a promise continuously in the fulfilled state.
A315054_1_En_21_Fig12_HTML.jpg
Figure 21-12.
Timeline of a promise having tasks added to it past resolution

Adding Error Catchers to a then( ) with the Catch Argument

Caution
Promises, let variables, and arrow functions are ES6 features. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support these features. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You want to be able to catch an error from a previous promise or then() that will be executed when the promise is rejected, or an error occurs in the previous then().

Solution

As mentioned, the Promise.prototype.then() function takes two arguments, both functions. The second argument is the rejectedAction argument, which is a function that is called if the state of the promise is rejected, and defaults to a function that throws the first given argument. rejectedAction will be called with the rejectResult. That is the value that was either passed to the reject() function in the original promise constructor, or the value that was thrown in the last resolveAction or rejectAction. The value that is returned from this function becomes the new resolveResult, and the promise goes back to a fulfilled state, unless this function throws in which case the value thrown becomes the rejectResult and the promise remains in a rejected state.

The Code

let writeFilePromise = new Promise((resolve, reject) => {
  fs.writeFile('data.txt', data, err => {
      if (err) {
          reject(err);
      } else {
          resolve();
      }
  });
});
writeFilePromise.then(undefined, () => console.log('Oh no, an error!'));
Listing 21-4.
Adding Error Catchers to a then() with the Catch Argument
The output is:
// When data.txt writes successfully
[nothing]
// When data.txt threw an error during write
Oh no, an error!

How It Works

Promise.prototype.then() allows you to add callbacks to any promise instance. The given callbacks are queued up and executed depending on the outcome state of the last callback. The functions passed to it work similarly to the promise constructor, in that the function calls themselves are not asynchronous and should not execute any asynchronous code. These callbacks differ from the promise constructor, in that they have no mechanism for deferring their return value, other than returning a new promise (which does). In other words, both of the functions you pass to it should use synchronous code or return promises that invoke asynchronous code.
then() can be called on a promise in any state. When the promise is in a pending state, it will wait until it is in a fulfilled or rejected before it executes any arguments in a then() function. If it is in the rejected state, then the queue is executed, one by one, calling each of the rejectedActions (the second argument in a then() call). A promise that’s already rejected will call the next rejectedAction in the queue. A promise can change state to fulfilled from a rejected state at any time, meaning that, while you can guarantee that one of the functions given to then() will be called, you can never guarantee which one it will be. Figure 21-13 illustrates the behaviors of a promise that rejects with a catch function in place.
A315054_1_En_21_Fig13_HTML.jpg
Figure 21-13.
Timeline of a promise being resolved and rejected by a then

Adding Error Catchers to the Promise Sequence with catch( )

Caution
Promises, let variables, and arrow functions are ES6 features. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support these features. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You want to be able to add an error catcher to a promise, but you are not concerned about the first argument in the then() function and need a simpler way to add an error catcher.

Solution

Promise.prototype.catch() is a simple shortcut for calling Promise.prototype.then() while omitting the first argument. catch() only takes one argument, a rejectedAction. This will be called only if the promise is in a rejected state. rejectedAction will be called with the rejectResult. That is the value that was either passed to the reject() function in the original promise constructor, or the value that was thrown in the last resolveAction or rejectAction. The value that is returned from this function becomes the new resolveResult, and the promise goes back to a fulfilled state, unless this function throws, in which case the value thrown becomes the rejectResult and the promise remains in a rejected state .

The Code

let writeFilePromise = new Promise((resolve, reject) => {
  fs.writeFile('data.txt', data, err => {
      if (err) {
          reject(err);
      } else {
          resolve();
      }
  });
});
writeFilePromise.catch(() => console.log('Oh no, an error!'));
Listing 21-5.
Adding Error Catchers to the Promise Sequence with catch()
The output is:
// When data.txt writes successfully
[nothing]
// When data.txt threw an error during write
Oh no, an error!

How It Works

Promise.prototype.catch() provided a convenient wrapper for Promise.prototype.then(undefined). Other than the arguments it takes, it behaves identically to its cousin: Promise.prototype.then().
catch() can be called on a promise in any state. When the promise is in a pending state, it will wait until it is fulfilled or rejected before it executes any arguments in a catch() function. If it is in the rejected state, then the queue is executed, one by one, calling each of the rejectedActions. A promise that’s already rejected will call the next rejectedAction in the queue. Figure 21-14 illustrates the behaviors of a promise that rejects with a catch function in place.
A315054_1_En_21_Fig14_HTML.jpg
Figure 21-14.
Timeline of a promise being resolved and rejected by a then

Making an Automatically Fulfilling Promise with Promise.resolve( )

Caution
Promises, let variables, and arrow functions are ES6 features. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support these features. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You have a value that you’d like to resolve into an immediately fulfilled promise.

Solution

Promise.resolve() takes one argument, which can be any value, that becomes the resolveResult as part of a new promise. It returns a new promise, which can then be chained off with then() and catch(). The first resolvedAction will be called with the resolveResult (the first argument given to Promise.resolve()).

The Code

let myResolveValue = 5;
// Long hand resolving Promise
let promise = new Promise(resolve => {
    resolve(myResolveValue);
});
// Shorthand Promise.resolve()
let promise = Promise.resolve(myResolveValue);
promise.then(value => console.log(value));
Listing 21-6.
Making an Automatically Fulfilling Promise with Promise.resolve()
The output is:
5

How It Works

Promise.resolve() automatically creates a new promise in the fulfilled state with the resolveResult set to the given argument. It offers exactly the same semantics as a new promise in which the resolve() function is immediately called with the given value.

Making an Automatically Rejecting Promise with Promise.reject( )

Caution
Promises, let variables, and arrow functions are ES6 features. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support these features. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You have a value that you’d like to resolve into an immediately rejected promise.

Solution

Promise.reject() takes one argument, which can be any value, that becomes the rejectResult as part of a new promise. It returns a new promise, which can then be chained off with then() and catch(). The first rejectedAction will be called with the rejectResult (the first argument given to Promise.resolve()).

The Code

let myRejectValue = new Error('My own error');
// Long hand rejecting Promise
let promise = new Promise((resolve, reject) => {
    resolve(myRejectValue);
});
// Shorthand Promise.reject()
let promise = Promise.reject(myRejectValue);
promise.catch((error) => console.log(error));
Listing 21-7.
Making an Automatically Rejecting Promise with Promise.reject()
The output is:
Error: My own error

How It Works

Promise.resolve() automatically creates a new promise in the rejected state with the rejectResult set to the given argument. It offers exactly the same semantics as a new promise, in which the reject() function is immediately called with the given value.
Remember, a rejected promise will automatically skip all resolvedActions and head for the first rejectedAction, but if the rejectedAction returns a value and doesn’t throw, then the promise can be put into a fulfilled state. See Figure 21-15.
A315054_1_En_21_Fig15_HTML.jpg
Figure 21-15.
Timeline of a promise being rejected and caught with catch

Working with Multiple Promises in Sequence

Caution
Promises, let variables, and arrow functions are ES6 features. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support these features. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You have multiple promises that you’d like to execute in serial (one after the other).

Solution

As part of the built-in behavior of a promise, if the return value from a resolvedAction or rejectedAction is a promise, then the returned promise has to be fulfilled or rejected, and all of its queue needs to be executed for the upstream promise queue to continue. In addition, if the state of the returned promise can alter the state of the upstream promise; for example, if the upstream promise is fulfilled but the returned promise ultimately ends up as rejected, it will change the state of the upstream promise to rejected.

The Code

new Promise((resolve, reject) => {
  fs.readFile('data.json', data, err => {
      if (err) {
          reject(err);
      } else {
          resolve();
      }
  });
}).then(fileData => JSON.parse(fileData))
.then(fileJSON => {
    fileJSON.version = Number(fileJSON.version) + 1;
    return fileJSON;
}).then(fileJSON => new Promise((resolve, reject) => {
    fs.writeFile(JSON.stringify('data.json', fileJSON, err => {
        if (err) {
            reject(err);
        } else {
            resolve();
    });
}).then(() => console.log('File wrote successfully'), err => console.log('Oh no!, err));
Listing 21-8.
Working with Multiple Promises in Sequence
The output is:
// After successful file read and write
File wrote successfully
// With any error in the Promise
Oh no! Error: ...

How It Works

In the code example, data.json’s contents are read, as part of a promise, parsed as JSON, the version number is incremented, and the contents are written back into data.json. The write operation is also wrapped in a promise, but one that is returned as part of a then() (the arrow function without curly braces immediately returns the only statement in its body).
The promise that is returned as part of the write operation dictates the rest of the flow of the parent most promise (the read operation). The first thing required is that the last then() is deferred for execution until the write operation completes, and the child promise is fully resolved with nothing left in its promise queue. The second thing that happens is the last state of the child promise is copied over to its parent, meaning if the child promise (the write file operation in this case) ends up in a rejected state, the parent promise (the read file operation in this case) will inherit that state and become rejected, even if it was originally fulfilled. This sounds like it may cause slip ups, but it is actually a very powerful tool when dealing with multiple, nested, promises. See Figures 21-16 and 21-17 for illustrations on how this might work .
A315054_1_En_21_Fig16_HTML.jpg
Figure 21-16.
Timeline of a simple set of nested promises
A315054_1_En_21_Fig17_HTML.jpg
Figure 21-17.
Timeline of a set of nested promises, where the child promise becomes rejected

Executing Code After the First in a List of Promises Resolves with Promise.race( )

Caution
Promises, let variables, and arrow functions are ES6 features. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support these features. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You have a list of promises, and you want to execute code after one of any of those promises resolves to a fulfilled or rejected. You do not care which one resolves first, just as long as one does.

Solution

Promise.race() takes one argument, which is an iterable (e.g., an array) of values. Any value that’s an instance of promise will be used to defer the execution of subsequent callbacks, until one of the promises in the iterable is fulfilled or rejected. All values that are not a promise will be converted into a promise using Promise.resolve().

The Code

Promise.race([1,2,3]).then((value) => console.log('The first resolved value is ', value);
Promise.all([Promise.reject(new Error('Errors reject!'))]).then(() => console.log('This wont fire'), (err) => console.log('Oh no!', err));
Listing 21-9.
Executing Code After the First in a List of Promises Resolves with Promise.race()
The output is:
1
Oh no!  Error: Errors reject!

How It Works

Internally, the interpreter calls Promise.resolve on all items in the iterable given to Promise.race(). Any values that aren’t promises are composed into immediately resolving promises, while any that are need to resolve before the promise returned from Promise.all() will be resolved.
The internal implementation (as per the spec) simply iterates through all values, calling Promise.resolve() on them, passing the returned promises resolve and reject as the resolvedHandler and rejectedHandler. A JavaScript implementation would look similar to Listing 21-10.
Promise.race = items => new Promise((resolve, reject) => {
    for(let item of items) {
        Promise.resolve(item).then(resolve, reject);
    }
});
Listing 21-10.
JavaScript Implementation of Promise.race

Working with Multiple Promises in Parallel with Promise.all( )

Caution
Promises, let variables, and arrow functions are ES6 features. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support these features. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You have multiple promises that you’d like to execute in parallel (all together at the same time).

Solution

Promise. all() takes one argument, which is an iterable (e.g., an array) of values. Any value that’s an instance of promise will be used to defer the resolution of the returned promise, until every promise is fulfilled or rejected.

The Code

Promise.all([ 1, 2, 3 ]).then(values => console.log('This Promise resolves immediately with ', values));
let neverEndingPromise = new Promise(() => {});
Promise.all([ 1, 2, 3, neverEndingPromise ]).then(() => console.log('This promise will never resolve'));
Promise.all([Promise.reject(new Error('Errors reject!'))]).then(() => console.log('This wont fire'), (err) => console.log('Oh no!', err));
Listing 21-11.
Working with Multiple Promises in Parallel with Promise.all()
The output is:
This Promise resolves immediately with  [1, 2, 3]
Oh no!  Error: Errors reject!

How It Works

Internally, the interpreter calls Promise.resolve on all items in the iterable given to Promise.all(). Any values that aren’t promises are composed into immediately resolving promises, while any that are need to resolve before the promise returned from Promise.all() will be resolved. Any promises that are in a rejected will put the parent promise into a rejected state.
The internal implementation (as per the spec) simply keeps a list of all elements in the iterable and a counter of all resolved promise values. When each promise is resolved, the counter ticks down until it reaches 0, when the returned promise is resolved. To put this in code, it might look something like Listing 21-12.
Promise.all = items => new Promise((resolve, reject) => {
    let incr = 0, values = [];
    for(let item of items) {
        let index = incr;
        Promise.resolve(item)
            .then((value) => {
                incr--;
                values[index] = value;
                if (incr === 0) {
                    resolve(values);
                }
            }, reject);
        incr++;
    }
});
Listing 21-12.
JavaScript Implementation of Promise.all

Making a Promise Utility Function that Waits a Given Amount of Time to Fulfill

Caution
Promises, let variables, and arrow functions are ES6 features. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support these features. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You want to be able to generate a promise that delays a certain amount of time before resolving.

Solution

Combining the promise constructor and setTimeout, you can easily create a function that takes an optional arbitrary value and an integer value representing the number of milliseconds to delay the promise by.

The Code

Promise.myextensions = Promise.myextensions || {};
Promise.myextensions.delay = (value, ms) => new Promise((resolve) => {
    if (typeof ms === 'undefined') {
        ms = value;
        value = undefined;
    }
    setTimeout(() => resolve(value), ms);
});
Promise.myextensions.delay('Hello, after 1 second', 1000)
    .then(console.log.bind(console));
Promise.resolve()
    .then(() => {
        return Promise.myextensions.delay(2000);
    })
    .then((value) => {
        console.log('Hi, after 2 seconds', value);
    });
Listing 21-13.
Making a Promise Utility Function that Waits a Given Amount of Time to Fulfill
The output is:
“Hello, after 1 second” [after 1 second]
“Hi, after 2 seconds”, undefined [after 2 seconds]

How It Works

First, a function is created on Promise.myextensions.delay. This could be set as Promise.delay, but if a later version of JavaScript implements this as a native feature, then there is a risk of incompatibility, so it is much safer to namespace custom extensions onto a new object (it is very unlikely that ECMAScript will ever define Promise.myextensions as anything, but feel free to choose an equally unlikely name, such as a company or organization name).
Promise.myextensions.delay = (value, ms) => new Promise((resolve) => {
The first line, other than defining the function, contains is an arrow function that immediately returns a new promise. Only the resolve argument has been defined, because this function can never enter a rejected state, as there is nothing to reject.
if (typeof ms === 'undefined') {
    ms = value;
    value = undefined;
}
The function takes two arguments, value and ms. value (the value to resolve the promise with) is optional, and so we need to write a small piece of detection logic that says if ms (the number of milliseconds to delay by) is undefined, then take the value from value, and treat that as ms, meanwhile set value to undefined. This allows for the two different method signatures: Promise.myextensions.delay(value, ms) and Promise.myextensions.delay(ms).
setTimeout(() => resolve(value), ms);
The core part of the functionality is incredibly simple. It simply calls setTimeout, which takes a callback and a millisecond value for which to wait before executing the callback. The callback function for setTimeout simply calls resolve with the given value. ms is supplied as the second parameter.

Making a Promise Utility that Ensures Slow Operations Time Out

Caution
Promises, let variables, and arrow functions are ES6 features. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support these features. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You want to be able to create a promise that takes an existing promise, aiming to resolve it within a given time. If the given promise exceeds the given time, the created promise will reject with a timeout.

Solution

Brief note about the solution
Perhaps another paragraph

The Code

Promise.myextensions = Promise.myextensions || {};
Promise.myextensions.timeout = (promise, ms) => new Promise((resolve, reject) => {
    promise.then(resolve, reject);
    setTimeout(() => reject(new Error('Timeout')), ms);
});
let neverResolvingPromise = new Promise(() => {});
Promise.myextensions.timeout(neverResolvingPromise, 2000)
    .then((value) => console.log(value), (error) => console.log(error));
Listing 21-14.
Making a Promise Utility that Ensures Slow Operations Time Out
The output is:
Error: Timeout [after 2 seconds]

How It Works

First, a function is created on Promise.myextensions.timeout. This could be set as Promise.timeout, but if a later version of JavaScript implements this as a native feature, then there is a risk of incompatibility, so it is much safer to namespace custom extensions onto a new object (it is very unlikely that ECMAScript will ever define Promise.myextensions as anything, but feel free to chose an equally unlikely name, such as a company or organization name).
Promise.myextensions.timeout = (promise, ms) => new Promise((resolve, reject) => {
The first line, other than defining the function, is an arrow function that immediately returns a new promise. The function itself takes two arguments—promise (an underlying promise to chain off of) and ms (the amount of time to wait before rejecting with a timeout error).
promise.then(resolve, reject);
The first line of the inner function simply tacks the resolve and reject arguments onto the given promise. This way, if the given promise resolves or rejects, then the created timeout promise will do the same.
setTimeout(() => reject(new Error('Timeout')), ms);
The core part of this function looks very simple, but relies on a subtle feature of promises. Effectively, just like the delay function, a setTimeout is called with a callback and ms. The difference here is that this callback call rejects, with a timeout error, to put the promise into a rejected state. The subtle feature of promises that this utilizes, is that a promise constructor’s reject() and resolve() functions have a built-in trap that they can only be called once between them. This means if a codebase (such as this one) called reject() multiple times, only the first reject() would actually count. Similarly if the code called reject() followed by resolve(), the resolve() call would not count. The promise has already moved away from its pending state, so these functions are effectively no-ops (they do nothing) .

Making a Promise Utility that Converts Node-Style Callbacks to Promises

Caution
Promises, rest parameters, and arrow functions are ES6 features. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support these features. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You want to be able to convert a Node.js-style asynchronous function, such as fs.writeFile, into a function that returns a promise.

Solution

Node.js’ asynchronous methods all follow the same callback signatures. The callback is always the last argument, and it is always called with an Error object (or null) as the first argument and an optional mixed value as the second. We can take advantage of this to write a generic wrapper that takes a Node.js-style asynchronous function and resolves or rejects a returned promise. Our method will take one fixed argument—the Node.js-style method—and an infinite number of optional arguments, and return a promise that will execute the behavior of the given method.

The Code

Promise.myextensions = Promise.myextensions || {};
Promise.myextensions.denode = (method, ...values) => new Promise((resolve, reject) => {
    method(...values.concat((error, value) => {
        if (error) {
            reject(error);
        } else {
            resolve(value);
        }
    }));
});
function nodeStyleFunc(msg, cb) {
    cb(null, msg);
}
function nodeStyleFuncWithError(msg, cb) {
    cb(new Error('An error occurred!'), null);
}
Promise.myextensions.denode(nodeStyleFunc, 'hello world')
    .then((value) => console.log(value));
Promise.myextensions.denode(nodeStyleFuncWithError, 'hello world')
    .catch((error) => console.log(error.message));
Listing 21-15.
Making a Promise Utility that Converts Node-Style Callbacks to Promises
The output is:
hello world
An error occurred!

How It Works

First, a function is created on Promise.myextensions.denode. This could be set as Promise.denode, but if later version of JavaScript implements this as a native feature, then there is a risk of incompatibility, so it is much safer to namespace custom extensions onto a new object (it is very unlikely that ECMAScript will ever define Promise.myextensions as anything, but feel free to chose an equally unlikely name, such as a company or organization name).
Promise.myextensions.denode = (method, ...values) => new Promise((resolve, reject) => {
The first line, other than defining the function, is an arrow function that immediately returns a new promise. The function itself takes our fixed method argument, which is the Node.js-style asynchronous function to call. It also uses the rest parameter (described in Chapter 12) to capture all additional arguments into an array.
method(...values.concat((err, value) => {
The main part of the functionality calls our Node.js-style asynchronous function, using the spread operator, so that all arguments are applied to the method, rather than it being passed one array of values. A callback function is also concatenated to the values array, ensuring that the last argument is always the callback that’s used to resolve the promise.
if (error) {
     reject(error);
 } else {
     resolve(value);
 }
When the callback is executed, it is simply a case of determining if the error argument is “truthy.” If it comes back as null, then it will be “falsy,” any other value we want to deal with and reject the promise. Otherwise we want to resolve the promise with the given value. This could be rewritten using a ternary operator; error ? reject(error) : resolve(value), but it makes the code a little more difficult to interpret.

Using Promises with Generators

Caution
Promises are an ES6 feature. Older browsers still in use, such as Internet Explorer 11 and below, or Safari 7 and below, do not support this feature. Check out http://kangax.github.io/es5-compat-table/es6/ for the current compatibility charts.

Problem

You want to be able to deal with promises, but write them in a synchronous-looking manner using the power of ES6 generators (without callbacks).

Solution

Because generators pause the function state until the yielded value calls next(), promises can be used in a very powerful combination with generators, to make a function that yields any time it wants asynchronous behavior to occur. We can write a method that will take a generator function, and iterate over it, each time taking the value and treating it like a promise, only calling next() when the value promise has resolved, and calling throw() when the promise is rejected.

The Code

Promise.myextensions = Promise.myextensions || {};
Promise.myextensions.coroutine = (generator) => (...values) => new Promise((resolve, reject) => {
    let iterator = generator(...values),
        iterate = (value) => {
            let iteration = iterator.next(value);
            if (iteration.done) {
                resolve(iteration.value);
            } else {
                Promise.resolve(iteration.value)
                    .then(iterate, (error) => {
                        iterator.throw(error);
                        reject(error);
                    }) ;
            }
        };
    iterate();
});
Listing 21-16.
Using Promises with Generators
The output is:
1

How It Works

First, a function is created on Promise.myextensions.coroutine. This could be set as Promise.coroutine, but if a later version of JavaScript implements this as a native feature, then there is a risk of incompatibility, so it is much safer to namespace custom extensions onto a new object (it is very unlikely that ECMAScript will ever define Promise.myextensions as anything, but feel free to chose an equally unlikely name, such as a company or organization name).
Promise.myextensions.coroutine = (generator) => (...values) => new Promise((resolve, reject) => {
The first line, other than defining the function, is an arrow function that immediately returns another arrow function. The returned arrow function, when called, returns a new promise. The reason the coroutine function returns an arrow function, is to allow composition. Functions can be created, wrapped in the coroutine method, and become new methods that can be executed later on in the code, as demonstrated in the examples. The promise is returned from the returned arrow function to allow nesting of coroutines. A coroutine() that yields to another coroutine() can use this promise to pause its execution.
let iterator = generator(...values),
This line simply starts the iterator, giving it the values passed from the calling arrow function. Generators, as described in Chapter 13, return an instance of GeneratorFunction, which offers the utility to iterate through the generator’s yielded values. This function needs to use that, so it stored the GeneratorFunction instance as iterator.
iterate = (value) => {
An iterate function is defined and will be called for every iteration the generator provides. It takes one argument, which will end up being the value of the last iteration.
let iteration = iterator.next(value);
Inside the iterate function, we get iterator’s next value by calling next(). It is provided the value of the last iteration, so that when the GeneratorFunction resumes, it can deal with the last value that was iterated on. The resulting value is stored as iteration, so that the following lines can determine what to do next. As described in Chapter 13, the resulting value from a GeneratorFunction next() call is an object with two properties: done and value.
if (iteration.done) {
    return resolve(iteration.value) ;
If the iteration’s done property is true, that means the iterator has finished all available statements (either it has returned a value or it hit the last line statement inside the function). At this point, coroutine’s promise is resolved with the last value (the return value, or undefined).
} else {
       Promise.resolve(iteration.value)
           .then(iterate, (error) => iterator.throw(error));
   }
If the iteration’s done property is false, that means the iterator has yet to finish, so next() needs to be called again. However before that, the current value needs to be dealt with. Promise.resolve is called on the value, to convert it into a promise (if it isn’t already). Casting everything as a promise simplifies the logic with how to deal with the iterator values, and provides a consistent asynchronous interface for the GeneratorFunction. As a promise, it is then simply a case of calling then(). The iterate function is given as the resolvedAction which will allow it to recurse. The cycle repeats again, and iterator.next() is called with the resolved value. The arrow function is the rejectedAction, and it calls iterator.throw(), which as described in Chapter 13, resumes the GeneratorFunction but with a thrown error.
iterate();
As a last step in the function, iterate needs to be called. A value does not need to be passed on the first iteration of a generator.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.115.154