This book is published under the Creative Commons Attribution-NonCommercial license. You can also download the PDF version from the link below.

Readers interested in the creation process of this ebook can download this document for reference (Japanese version) production process [3].

  • Why this book was written, how it was written, and how the writing process works.

  • This appendix can be purchased on Gumroad for any price including $0.

  • If you would like to donate, you can also do so by purchasing this appendix at any price through this appendix link.


The purpose of this book is to introduce readers to JavaScript support for Promise-related technologies, centered on the currently being developed ECMAScript 6 Promises specification.

By reading this book, we hope you will learn three things.

  • Learn Promise related content, be able to skillfully use Promise pattern and test

  • Learn what Promise is suitable for, what is not suitable for, know that Promise is not omnipotent, can not want to use Promise to solve everything

  • Based on ES6 Promises, I will gradually develop my own style

As mentioned above, this book is based on ES6 Promises, the JavaScript standard specification, and Promises.

Promises don’t require an extra plug-in to use Promise on technologically advanced browsers like Firefox and Chrome, and Promises is A Promise /A+ community specification that has many implementations.

We’ll start with the base API and introduce Promise functionality that can be supported natively in browsers or through plug-ins. I also hope that readers can understand what Promise is suitable for, what is not suitable for, according to the actual needs to choose the appropriate technical implementation scheme.

Before you start reading

The readers of this book should have a basic understanding and knowledge of JavaScript.

If you read one of the books above, it should be very easy to understand.

In addition, some of this article will be familiar to you if you have experience writing Web applications in JavaScript, or command-line, server-side applications in Node.js.

One chapter of this book will be explained in the context of the Node.js environment, which should be very easy to understand if you have the basics of Node.js.

In order to save space, the following formatting conventions are used.

  • See glossary for terms about promises.

    • The first occurrence of a noun is usually accompanied by a related link.

  • Instance methods are all in the form of Instance# method.

    • For example, Promise#then represents the then method of an instance of a Promise.

  • Object methods take the form object.method.

    • This follows the way it is used in JavaScript, where promise.all represents a static method.

This part of the content is mainly about the text part of the supplementary explanation.

Recommended Browser

We recommend using a browser with built-in Promise support to read this book.

Firefox and Chrome support ES6 Promises.

In addition, the book can be read on mobile devices such as iOS, although it is not a recommended reading environment.

Run the sample code

This site uses Promise’s Polyfill library, so the sample code can be executed even on browsers that don’t support Promise.

In addition, readers can run the executable sample code by running the button, as shown below.

 promise =  Promise(function(resolve){
    resolve();
});
promise.then(function(value){
    console.log(value);
}).catch(function(error){
    console.error(error);
});Copy the code

When the button is pressed, the code area becomes an edit area and the code is executed. You can also run the code again using this button.


Button used to clear by
console.logLog printed out.


Button to exit edit mode.

If you have any questions, you can modify the code on the spot and execute it to deepen your understanding of that part of the code.

Book source code /License

The sample code in this book can be found on GitHub.

The book is written in AsciiDoc[4] format.

The repository also contains test code for the sample code in this book.

The source code is licensed under MIT license, and the article content can be used based on CC-BY-NC.

Comments and questions

If you have a comment or question, just Issue it on GitHub.

In addition, you can also leave a message on chat online [5].

In addition to reading the book for free, readers also have the right to edit the book. You can contribute on GitHub through Pull Requests[6].

1. Chapter.1 – What is Promise

This chapter focuses on getting started with promises in JavaScript.

1.1. What is Promise

First, let’s take a look at what promises really are.

Promises are components that abstract asynchronous processing objects and perform various operations on them. We’ll cover this in more detail later, but promises weren’t invented in JavaScript.

Promise was first proposed in E language [7], which is a programming language based on parallel/parallel processing design.

Now that JavaScript has this feature, it’s the JavaScript Promise that this book introduces.

On the other hand, when it comes to javascript-based asynchronous processing, I think most people think of using callback functions.


----
getAsync(fileA.txt, function(error, result){(1)(error){// Throw error; } // Handle success}); ---- <> (error )Copy the code

Node.js and others specify that the first argument to a JavaScript callback is an Error object, which is also a convention.

Callback-based asynchronous processing like the one above would also be straightforward if the same rules were used for arguments. However, this is just a coding convention, and you can’t go wrong writing it differently.

Promises formalize similar asynchronous processing objects and processing rules, and write them in a unified interface. Writing anything other than the prescribed method would be a mistake.

Promise
----
 promise = getAsyncPromise(fileA.txt); (1)Then (function(result){catch(function(error){// fetch file (error)}); ---- <> promiseCopy the code

We can register the callback function for success and failure with the Promise object, which is preset for abstract asynchronous processing.

How does this differ from the callback method? When we use promises for one-step processing, we have to write the processing code as the interface dictates.

In other words, methods other than the methods specified by the Promise object (then or catch) are not allowed. Instead of defining the parameters of the callback function, you must strictly follow a fixed and uniform programming method to write code.

Thus, a unified interface based on Promise can lead to a variety of asynchronous processing patterns based on the interface.

So promise’s ability to easily model complex asynchronous processing is arguably one reason to use promises.

Next, let’s learn JavaScript’s Promise in action.

1.2 introduction of Promise

There are not many apis defined in the ES6 Promises standard.

At present, there are roughly the following three types.

Constructor

Promise is similar to XMLHttpRequest in that a new new Promise object is created as an interface from the constructor Promise.

To create a Promise object, instantiate it by calling the Promise constructor with new.

Promise = promise (function(resolve, reject) {// call resolve or reject});Copy the code

Instance Method

The promise.then() instance method can be used for callbacks that are called in resolve(success)/reject(failure) to set the value of a promise object generated by new.

promise.then(onFulfilled, onRejected)Copy the code

Resolve

Ondepressing will be called

Reject (reject

OnRejected is called

Ondepressing and onRejected are both optional parameters.

Promise. then can be used for both success and failure. In addition, if you only want to handle exceptions, you can use promise.then(undefined, onRejected), and only specify the callback function in reject. In this case promise.catch(onRejected) would be a better option.

promise.catch(onRejected)Copy the code

Static Method

Global objects like Promises also have static methods.

Promise.all() and promise.resolve () are helper methods that operate on promises.

1.2.1. The Promise of workflow

Let’s take a look at the following example code.

promise-workflow.js
function asyncFunction() {
    (1)return Promise(function (resolve, reject) { setTimeout(function () { resolve(Async Hello world); }); }); }(2)
asyncFunction().then(function (value) {
    console.log(value);    // => 'Async Hello world'
}).catch(function (error) {
    console.log(error);
});Copy the code

1 newAfter the Promise constructor, a Promise object is returned
2 <1> Uses Settings for the Promise object.thenThe callback function when the return value is called.

AsyncFunction returns a Promise object, for which we call the then method to set the resolve callback and the catch method to set the error callback.

The promise object will be resolved 16ms after setTimeout, when the then callback will be called and output ‘Async Hello World ‘.

The catch callback will not be executed in this case (because promise returns resolve), but if the runtime environment does not provide a setTimeout function, the above code will raise an exception and the callback set in the catch will be executed.

Of course, like the promise. Then (onFulfilled, onRejected) method declaration, the code shown below can do the same if you use only the THEN method without the catch method.

asyncFunction().then(function (value) {
    console.log(value);
}, function (error) {
    console.log(error);
});Copy the code

1.2.2. State of Promise

Now that we have an overview of how promises work, let’s sort out the Promise states a little bit.

A Promise object instantiated with New Promise has three states.

“has-resolution” – Fulfilled

Resolve. Ondepressing will be called at this time

“has-rejection” – Rejected

Reject (reject) OnRejected is called

“unresolved” – Pending

It is neither resolve nor Reject. That’s the initial state of the promise object after it’s created

Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/ B

Basically, the state is not covered in the code, so the name doesn’t matter too much. In this book, we will talk about Pending, depressing and Rejected states in Promises/A+[8].

Figure 1. promise states

[[PromiseStatus]] is defined internally in the ECMAScript Language Specification ECMA-262 6th Edition — DRAFT. As there is no public user API for accessing [[PromiseStatus]], there is no way to query its internal state.

So far in this article we’ve covered all three states of promise.

After the state of the Promise object is changed from Pending to Fulfilled or Rejected, the state of the Promise object will not change again.

That is, unlike, say, events, functions that are executed after. Then can be said with certainty to be called only once.

In addition, either of the states of depressing and Rejected can be represented as Settled.

Settled

Resolve or reject.

From the symmetry between Pending and Settled, the type/migration of Promise states is very straightforward.

Use.then to define functions that will be called only once when the state of the promise object changes.

JavaScript Promises – Thinking Sync in an Async World // Speaker Deck[9] this PPT contains very easy to understand instructions about Promise state transfer.

1.3. Write Promise code

Here’s how to write Promise code.

1.3.1. Create promise objects

The process for creating a Promise object is shown below.

  1. New Promise(fn) returns a Promise object

  2. Specify asynchronous etc processing in FN

    • If the result is normal, call resolve(process result value)

    • Call reject(Error object) if the result is incorrect

Let’s follow this process and actually write the Promise code.

Our task is to use Promise to retrieve XMLHttpRequest(XHR) data via asynchronous processing.

Create the XHR Promise object

First, create a function called getURL that wraps the XHR processing in Promise.

xhr-promise.js
function getURL(URL) { return Promise(function (resolve, reject) { req = XMLHttpRequest(); req.open(, URL, ); req.onload = function () { (req.status === ) { resolve(req.responseText); } { reject( Error(req.statusText)); }}; req.onerror = function () { reject( Error(req.statusText)); }; req.send(); }); } // Run the sample URL = http://httpbin.org/get; getURL(URL).then(function onFulfilled(value){ console.log(value); }).catch(function onRejected(error){ console.error(error); });Copy the code

GetURL will only call resolve – reject – if the result status is 200 obtained through XHR, and reject if the data is successful.

Resolve (req.responsetext) adds a parameter to the content of response. There are no specific rules for the parameters of the resolve method; it basically just passes the parameters to the callback function. (The then method can receive this parameter value)

For those familiar with Node.js, the first argument to callback(error, response) is set to an error object, while resolve/reject handles both normal and abnormal cases in Promise. So it’s okay to pass just one response parameter in the resolve method.

Let’s look at the reject function.

Reject is called when the ONError event in XHR is raised. Here we focus on the value passed to reject.

Reject (new Error(req.statustext))); Create an Error object and pass in the value. There are no special restrictions on the arguments passed to reject, which are generally Error objects (or inherited from Error objects).

Reject, which is typically an Error object containing the reject reason. Reject is rejected because the status value is not equal to 200, so we put statusText in REJECT. (The value of this argument can be used in the second argument to the then method or in the catch method.)

1.3.2. Write promise object handling methods

Let’s use the function we just created that returns a Promise object in action

getURL(http://example.com/); // => Returns the Promise objectCopy the code

As outlined in Promises Overview, the Promise object has several instance methods that we use to create callback functions for the Promise object that depend on the specific state of the promise and will be executed only once.

There are two main ways to add processing to a Promise object

  • Ondepressing when the promise object is resolved

  • A promise object is rejected (onRejected)

Figure 2. promise value flow

First, let’s try the handler that is added when getURL communicates successfully and fetches a value.

At this time, the so-called successful communication means that after the promise object is resolved, it will become a pity state.

If resolve is resolved, the desired function can be passed in the. Then method.

 URL = http://httpbin.org/get;
getURL(URL).then(function onFulfilled(value){ (1)
    console.log(value);
});Copy the code

1 So let’s call this function just to make it easier to understandonFulfilled

Resolve (req.responsetext) in getURL; The promise object will change to the resolve (depressing) state, and the ondepressing function will be called with its value.

So far we haven’t done anything to deal with any errors that might occur, so let’s add exception handling to the getURL function in case of an error.

The reject state is Rejected, and the promise object changes to Rejected.

Reject, either in the second argument to.then or in the.catch method, which sets the desired function to be called.

Add the reject to the previous code, as shown below.

 URL = http://httpbin.org/status/500; (1)
getURL(URL).then(function onFulfilled(value){
    console.log(value);
}).catch(function onRejected(error){ (2)
    console.error(error);
});Copy the code

1 The status code returned by the server is 500
2 The function is called theta for the sake of understandingonRejected

When any exception occurs in getURL processing, or is explicitly rejected, the cause of the exception (the Error object) is called as an argument to the.catch method.

Catch is just an alias for promise.then(undefined, onRejected).

getURL(URL).then(onFulfilled, onRejected);(1)Copy the code

1 Ondepressing, onRejected is the same function as before

In general, it is recommended to write resolve and reject separately using.catch, which is explained in more detail in then and catch.

In this chapter, we briefly introduced the following contents:

  • Create a Promise object with the New Promise method

  • Add a handler for the Promise object with. Then or. Catch

So far we’ve learned the basics of writing promises. Much of the other processing extends from this basic syntax and is implemented using static methods that Promise provides.

In fact, the same work could have been done using the callback method, but what’s the advantage of using Promise? In this section we don’t talk about the comparison or the benefits of promises. In the sections that follow, we’ll look at one of Promise’s benefits, its error-handling mechanism, and how it compares to traditional callbacks.

2. Chapter.2 – Actual Combat Promise

In this chapter we’ll look at the various methods Promise offers and how to handle errors.

2.1. Promise. Resolve

Normally we use new Promise() to create a Promise object, but we can use other methods as well.

2.1.1. Shortcut for New Promise

The static promise.resolve (value) method can be thought of as a shortcut to the new Promise() method.

Such as Promise. Resolve (42); Think of it as syntactic sugar for the following code.

 Promise(function(resolve){
    resolve();
});Copy the code

Resolve (42) in this code; This will immediately put the Promise object into the resolved state and pass 42 to the onFulfilled function specified in the later THEN.

Method Promise. Resolve (value); The return value of is also a Promise object, so we can follow up with a.then call to its return value as follows.

Promise.resolve().then(function(value){
    console.log(value);
});Copy the code

Promise.resolve is a shortcut to new Promise(), which is handy when initializing a Promise object or writing test code.

2.1.2. Thenable

The Promise. Resolve method also serves to convert the Thenable object into a Promise object.

ES6 Promises Promises about Thenable. In a nutshell, it is a very similar promise.

Just as we sometimes call a non-array object with a.length method Array like, thenable refers to an object with a.then method.

This mechanism for converting Thenable into Promise requires that the THEN methods owned by Thenable should have the same functions and processing as the THEN methods owned by Promise. When converting Thenable into Promise, It also makes clever use of the thenable object’s original THEN methods.

The simplest example of a thenable object is jquery.ajax ()[10], which returns thenable.

Because the return value of jquery.ajax () is a jqXHR Object, this Object has.then methods.

.ajax(/json/comment.json); // => Objects with '. Then 'methodsCopy the code

This Thenable object can be converted to a Promise object using promise.resolve.

Promises are Promises that allow you to use “then” or “catch” — methods defined in ES6 Promises.

Convert the Thenable object into a Promise object
promise = Promise.resolve(.ajax(/json/comment.json)); Promise. then(function(value){console.log(value); });Copy the code

JQuery and thenable

But the Deferred Object does not follow Promises/A+ or ES6 Promises standards, so even if it looks like the Object has been converted into A promise Object, there will be some missing information.

The root of this problem is that jQuery’s Deferred Object[11] has a different then method mechanism than promise.

So it should be noted that even if an object has.then methods, it does not necessarily promise to work as ES6 Promises objects.

Promise. Resolve uses only the common method then, providing the ability to convert Promise objects between different libraries.

This conversion to Thenable was previously done using promise.cast, and it’s not hard to imagine what it could do from its name.

Aside from the need to know something about Thenable when writing software such as libraries that use Promise, we probably don’t use this feature when we’re typically using it as end-user.

Resolve and Thenable will be explained in detail in Chapter 4, showing specific examples of a combination of Thenable and Promise.resolve.

The Promise. Resolve method will fulfill the parameters passed to it and return the Promise object.

In addition, many Promise processing uses the Promise.resolve algorithm to convert a value to a Promise object before processing it.

2.2. Promise. Reject

For example, promise.reject (new Error(” Error “)) is the syntactic sugar form of the code below.

 Promise(function(resolve,reject){
    reject( Error());
});Copy the code

The function of this code is to call the Promise object through the onRejected function specified by then and pass the Error object to the onRejected function.

Promise.reject( Error(BOOM!) ).catch(function(error){ console.error(error); });Copy the code

Resolve (value) differs from promise.resolve (value) in that the Promise calls reject instead of resolve, which may come in handy when writing test code or debugging.

2.3. Column: Can promises only operate asynchronously?

Resolve (value) if the Promise object immediately goes into the resolve state, do you think the.then method is called synchronously?

In fact, the method calls specified in.then are made asynchronously.

 promise =  Promise(function (resolve){
    console.log(inner promise); 
    resolve();
});
promise.then(function(value){
    console.log(value); 
});
console.log(outer promise); Copy the code

Execution of the above code outputs the following logs, from which we clearly know the order in which the above code is executed.

inner promise // 1
outer promise // 2
42            // 3

Because the JavaScript code is executed from top to bottom of the file, first <1> is executed, then resolve(42); Be implemented. At this time, the promise object has become a certain state, which is set to 42.

The following code promise.then registers the <3> callback, which is the focus of this column.

Since promise.then is executed when the Promise object is already in a determined state, it makes sense programmatically to call the callback simultaneously.

But even if the promise object is already in a deterministic state when the promise.then callback is called, promises call the callback asynchronously, which is the stated policy in the promise design.

So <2> is called first and the callback <3> is called last.

Why call a function asynchronously when it can be called synchronously?

2.3.1. Confusion caused by simultaneous and asynchronous invocation

This problem also exists outside of promises, and we’ll consider it in general usage.

The essence of the problem is that the function receiving the callback can choose whether to call the callback synchronously or asynchronously, depending on its execution.

Let’s take onReady(fn) as an example, which receives a callback function for processing.

mixed-onready.js
function onReady(fn) {
     readyState = document.readyState;
     (readyState === interactive || readyState === complete) {
        fn();
    }  {
        window.addEventListener(DOMContentLoaded, fn);
    }
}
onReady(function () {
    console.log(DOM fully loaded and parsed);
});
console.log(==Starting==);Copy the code

Mixed-onready. js determines whether to call the callback synchronously or asynchronously based on whether the DOM has been loaded at execution time.

If the DOM is already loaded before onReady is called

Make synchronous calls to the callback function

If the DOM has not been loaded before onReady is called

Make asynchronous calls to the callback function by registering the DOMContentLoaded event listener

Therefore, if this code appears differently in the source file, the order of the log messages printed on the console will be different.

To solve this problem, we can choose to use asynchronous invocation uniformly.

async-onready.js
function onReady(fn) {
     readyState = document.readyState;
     (readyState === interactive || readyState === complete) {
        setTimeout(fn, );
    }  {
        window.addEventListener(DOMContentLoaded, fn);
    }
}
onReady(function () {
    console.log(DOM fully loaded and parsed);
});
console.log(==Starting==);Copy the code

This problem is also covered in Effective JavaScript[12], item 67, do not make synchronous calls to asynchronous callback functions.

  • Synchronous calls to asynchronous callback functions, even when the data is ready, should never be made.

  • If asynchronous callback functions are called synchronously, the processing order may not be as expected, possibly with unintended consequences.

  • Synchronous calls to asynchronous callback functions can also lead to stack overflow or exception handling errors.

  • If you want to call an asynchronous callback at some point in the future, you can use an asynchronous API like setTimeout.

Effective JavaScript


– David Herman

Then also falls into this category. In order to avoid the confusion caused by simultaneous synchronous and asynchronous invocations mentioned above, the promise specification specifies that promises can only use asynchronous invocations.

Finally, if you rewrite the onReady function above with Promise, the code looks like this.

onready-as-promise.js
function onReadyPromise() { return Promise(function (resolve, reject) { readyState = document.readyState; (readyState === interactive || readyState === complete) { resolve(); } { window.addEventListener(DOMContentLoaded, resolve); }}); } onReadyPromise().then(function () { console.log(DOM fully loaded and parsed); }); console.log(==Starting==);Copy the code

Since Promise guarantees that each call will be asynchronous, we don’t need to call setTimeout to implement the asynchronous call ourselves in the actual code.

2.4. Promise# then

In the previous section we explained the use of the basic instance methods of Promise, then and catch.

Then (). Catch (). In fact, any method can be linked together as a method chain in a Promise.

Promise can be written as a method chain
aPromise.then(function taskA(value){
// task A
}).then(function taskB(vaue){
// task B
}).catch(function onRejected(error){
    console.log(error);
});Copy the code

If each callback function registered in THEN is called a task, then we can use the Promise method chain to write logic that can be processed in a taskA → Task B process.

The Promise chain is a bit long, so we’ll simplify it to the Promise chain.

Promise chain is one of the reasons why promises are good for writing applications with more asynchronous processing.

In this section, we will focus on the behavior and flow of the promise chain using THEN.

Against 2.4.1. Promise chain

In chapter 1, we see a simple example of then → catch in the Promise chain. If we make the method chain longer, how will the onFulfilled and onRejected registered in each promise object be implemented?

Promise chain – the shorter the method chain, the better. In this example we chose a long method chain for convenience.

Let’s take a look at the following promise chain.

promise-then-catch-flow.js
function taskA() {
    console.log(Task A);
}
function taskB() {
    console.log(Task B);
}
function onRejected(error) {
    console.log(Catch Error: A or B, error);
}
function finalTask() {
    console.log(Final Task);
}

 promise = Promise.resolve();
promise
    .then(taskA)
    .then(taskB)
    .catch(onRejected)
    .then(finalTask);Copy the code

The execution flow of the Promise chain in the code above, if depicted in a diagram, would look like this.

Figure 3. Promise-then-catch-flow. js

In the code above, we do not specify a second parameter (onRejected) to the then method, which can also be understood as follows.

then

Register the callback function during Ondepressing

catch

The callback function when onRejected is registered

Task A and Task B have A line pointing to onRejected.

These lines mean that in the process of Task A or Task B, the onRejected method is called in the following case.

  • When an exception occurs

  • Returns a Promise object in the Rejected state

As we saw in Chapter 1, the processing in promises is conventionally try-catch style, and when an exception occurs, it is caught by a catch and error-handled by a callback registered in that function.

Another exception-handling strategy is to return a Promise object with the Rejected state. This method calls onRejected in the Promise chain without using a throw.

I won’t discuss this method in detail because it’s not relevant in this section, but refer to Using Reject rather than Throw in Chapter 4.

In addition, in the Promise chain, there is no catch after the onRejected and Final Task, so any exception in these two tasks will not be caught. This should be noted.

Task A → onRejected = Task A → onRejected

Example of an exception generated by Task A

If an exception occurs in TaskA, the system deals with it according to the TaskA → onRejected → FinalTask process.

Figure 4. Schematic diagram when Task A generates an exception

The above process would look like this in code.

promise-then-taska-throw.js
function taskA() { console.log(Task A); throw Error(throw Error @ Task A) } function taskB() { console.log(Task B); } function onRejected(error) {console.log(error);} function onRejected(error) {console.log(error); // => "throw Error @ Task A" } function finalTask() { console.log(Final Task); } promise = Promise.resolve(); promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask);Copy the code

Task B will not be called by executing this code.

In this case we deliberately created an exception in taskA using the throw method. In practice, if you want to call onRejected, you should return a Promise object with the Rejected state. For details on the similarities and differences between the two methods, see the instruction in Using Reject rather than Throw.

2.4.2. How to pass parameters in promise chain

The tasks in the previous examples are independent of each other and are simply called.

What if Task A wants to pass A parameter to Task B?

The answer is simple: the value returned by the return in Task A is passed to Task B when it executes.

Let’s start with a concrete example.

promise-then-passing-value.js
function doubleUp(value) { return value * ; } function increment(value) { return value + ; } function output(value) { console.log(value); // => (1 + 1) * 2 } promise = Promise.resolve(); Promise.then (increment).then(doubleUp).then(output).catch(function(error){// The promise chain will be called when an exception occurs console.error(error); });Copy the code

The entry function for this code is promise.resolve (1); , the overall promise chain execution process is shown as follows.

  1. Promise.resolve(1); Pass 1 to increment

  2. Increment increments the received argument by +1 and returns (via return)

  3. The argument becomes 2 and is passed again to the doubleUp function

  4. Finally, the result is printed in function output

Figure 5. Schematic diagram of promise-then-passing-value.js

The return value in each method is not limited to a string or numeric type, but can also be an object or a promise object.

Resolve (the return value of return); Wrap accordingly, so that no matter what value is returned in the callback function, the result of the final THEN is a newly created Promise object.

You can refer to this partColumn: Each call to THEN returns a newly created Promise objectSome common mistakes are also covered there.

That is, Promise#then does more than just register a callback function, it transforms the return value of the callback function to create and return a Promise object.

2.5. Promise# catch

In the previous section of Promise#then, we have simply used the Promise#catch method.

Promissory #catch is just a promise. Then (undefined, onRejected); This is just an alias for the method. That is, this method is used to register the callback function when the Promise object state changes to Rejected.

2.5.1. Problems with IE8

[13]

[14]

The figure above is the result of the following code being executed on a browser using Polyfill [15].

Polyfill is a Library that supports using a feature on a browser that doesn’t have one. The example we use here comes from jakearchibald/es6-promise[16].

Run result of the Promise#catch
 promise = Promise.reject( Error(message));
promise.catch(function (error) {
    console.error(error);
});Copy the code

If we execute this code in various browsers, the identifier Not found syntax error will appear in IE8 and below.

What’s going on here? It actually has something to do with catch being an ECMAScript Reserved Word.

In ECMAScript 3 reserved words cannot be used as object property names. IE8 and below are implemented in ECMAScript 3, so you can’t use catch as an attribute, which means you can’t write code like promise.catch(), and hence the syntax error identifier Not found.

Today’s browsers are based on ECMAScript 5, and in ECMAScript 5 reserved words are identifierNames, which can also be used as property names.

In ECMAScript5, reserved words cannot be used as identifiers, i.e. variable names or method names. If we define a variable named for, we cannot distinguish it from the loop’s for. As property names, it is still easy to distinguish object.for from for, so we should be able to accept reserved words as property names.

Of course, there are ways to get around the ECMAScript 3 reserved words.

Dot notation requires that the attributes of the object be valid identifiers (reserved words cannot be used in ECMAScript 3),

But using bracket notation, you can use an invalid identifier as an attribute name for an object.

That is, the above code, if rewritten as follows, should run in IE8 and below (polyfill, of course).

Resolve conflict of Promise#catch identifiers
 promise = Promise.reject( Error(message));
promise[catch](function (error) {
    console.error(error);
});Copy the code

Or we can avoid this problem by using then instead of just catch.

Use promisethen instead of promiseCatch
 promise = Promise.reject( Error(message));
promise.then(undefined, function (error) {
    console.error(error);
});Copy the code

Because the catch identifier can cause problems, some libraries also use caught for the function name, which does the same job.

And many compression tools come with the ability to convert promise.catch to a promise[“catch”], so they may inadvertently help us solve this problem too.

Keep this catch issue in mind if you need a browser that supports IE8 or below.

2.6. Column: Each call to THEN returns a newly created Promise object

At first glance in code, apromise.then (…) .catch(…) Like a series of method chain calls to the original aPromise object.

In reality, however, both the THEN and catch method calls return a new Promise object.

Let’s take a look at how to make sure that the two methods return a new Promise object.

aPromise = Promise(function (resolve) { resolve(); }); thenPromise = aPromise.then(function (value) { console.log(value); }); catchPromise = thenPromise.catch(function (error) { console.error(error); }); console.log(aPromise ! == thenPromise); // => true console.log(thenPromise ! == catchPromise); // => trueCopy the code

=== is the strict equality comparison operator, and we can see that these three objects are different from each other, which proves that then and catch both return different promise objects from the caller.

We need to keep this in mind when we extend promises, or we could inadvertently treat the wrong Promise object.

If we know that the THEN method creates and returns a new promise object each time, it should be easy to understand the difference in how we use then in the following code.

APromise = promise (function (resolve) {resolve(); }); aPromise.then(function (value) { return value * ; }); aPromise.then(function (value) { return value * ; }); aPromise.then(function (value) { console.log( + value); BPromise = promise (function (resolve) {resolve(); }); bPromise.then(function (value) { return value * ; }).then(function (value) { return value * ; }).then(function (value) { console.log( + value); // => 100 * 2 * 2});Copy the code

The first version does not use the promise method chain, which should be avoided in promises. The THEN calls in this way start executing almost at the same time, and the value passed to each THEN method is 100.

In the second rule, the method chain is used to chain multiple THEN method calls together. Each function will be executed in the strict order of resolve → THEN → then → then. And the value passed to each THEN method is the value returned by the return from the previous Promise object.

The following is a typical example of an antipattern that can easily arise as a result of the use of THEN in method 1.


thenThe wrong way to use
function badAsyncCall() { promise = Promise.resolve(); Promise.then (function() {return newVar; }); return promise; }Copy the code

There are a number of problems with this approach. First, exceptions generated in promise.then are not caught externally, and you cannot get a return value for then, even if it does.

Because each promise.then call returns a newly created Promise object, you need to chain the call as in approach 2 above, using the Promise chain, as shown in the modified code below.

thenReturn Returns the newly created Promise object
function anAsyncCall() { promise = Promise.resolve(); Return promise.then(function() {return newVar; }); }Copy the code

For details on these anti-patterns, see the Promise Anti-Patterns [17].

The behavior of this function runs through the Promise as a whole, including promise. all and promise. race, which we’ll describe later, both of which accept a Promise object as a parameter and return a new Promise object that is different from the one that received the parameter.

2.7. Promises and Arrays

So far we’ve learned how to register callback functions with.then and.catch. These callback functions are called after the Promise object becomes a pity or Rejected state.

This will be a big pity. If there is only one promise object, we can write the code as described above. What will happen if several promise objects will become a big pity?

Let’s take the example of doing something when all XHR (asynchronous processing) has finished.

While it may be hard for readers to picture such a scenario in their heads, let’s take a look at the following XHR processing code that uses the usual callback function style.

2.7.1. Make multiple asynchronous calls through callback mode

multiple-xhr-callback.js
function getURLCallback(URL, callback) { req = XMLHttpRequest(); req.open(, URL, ); req.onload = function () { (req.status === ) { callback(, req.responseText); } { callback( Error(req.statusText), req.response); }}; req.onerror = function () { callback( Error(req.statusText)); }; req.send(); Function jsonParse(callback, error, value) {(error) {callback(error, value); } { { result = JSON.parse(value); callback(, result); } catch (e) { callback(e, value); // <2> Request = {comment: function getComment(callback) { return getURLCallback(http://azu.github.io/promises-book/json/comment.json, jsonParse.bind(, callback)); }, people: function getPeople(callback) { return getURLCallback(http://azu.github.io/promises-book/json/people.json, jsonParse.bind(, callback)); }}; // <3> Start multiple XHR requests, Callback function allRequest(requests, callback, results) {(requests. Length ===) {return callback(, results); } req = requests.shift(); req(function (error, value) { (error) { callback(error, value); } { results.push(value); allRequest(requests, callback, results); }}); } function (callback) { allRequest([request.comment, request.people], callback, []); } main(function(error, results){(error){return console.error(error); } console.log(results); });Copy the code

This callback style code has the following points.

  • Using the json. parse function directly might throw an exception, so a wrapper function, jsonParse, is used

  • The hierarchy is deeper if multiple XHR processes are nested, so the allRequest function is used and request is called from there.

  • The callback function is written as callback(error,value), with the first parameter representing the error message and the second parameter representing the return value

Using the jsonParse Function we used bind to reduce the need for anonymous functions by using Partial functions. (The number of anonymous functions can also be reduced if the separation of functions can be achieved in call-back style code.)

jsonParse.bind(, callback); Function bindJSONParse(error, value){jsonParse(callback, error, value); }Copy the code

In this callback style code, we can also see the following problems.

  • Need to display for exception handling

  • To keep the nesting level from getting too deep, you need a function that handles the request

  • Callbacks are everywhere

Let’s take a look at using Promise#then to do the same thing.

2.7.2. Handle multiple asynchronous requests simultaneously using Promise# THEN

It should be noted that promise.all fits the needs of this application scenario, so we deliberately use a lot of. Then arcane notation.

If.then is used, it does not correspond to the style of the callback.

multiple-xhr.js
function getURL(URL) { return Promise(function (resolve, reject) { req = XMLHttpRequest(); req.open(, URL, ); req.onload = function () { (req.status === ) { resolve(req.responseText); } { reject( Error(req.statusText)); }}; req.onerror = function () { reject( Error(req.statusText)); }; req.send(); }); } request = { comment: function getComment() { return getURL(http://azu.github.io/promises-book/json/comment.json).then(JSON.parse); }, people: function getPeople() { return getURL(http://azu.github.io/promises-book/json/people.json).then(JSON.parse); }}; function () { function recordValue(results, value) { results.push(value); return results; PushValue = recordValue.bind(, []); pushValue = recordValue.bind(, []); return request.comment().then(pushValue).then(request.people).then(pushValue); } main().then(function (value) {console.log(value); }).catch(function(error){ console.error(error); });Copy the code

Comparing the above code to the callback function style, we can conclude the following.

  • You can use the json.parse function directly

  • The function main() returns a promise object

  • Error handling takes place directly on the returned Promise object

As we said earlier, the then part of Main is a bit opaque.

To deal with this scenario where multiple asynchronous calls need to be handled uniformly, Promise prepares static methods promise. all and promise.race.

We will explain these two functions in the following sections.

2.8. Promise. All

Promise.all takes an array of Promise objects as arguments, and calls the.then method when all the Promise objects in the array are resolved or reject.

In the example we saw earlier of getting the results of several XHR requests in bulk, the code would have been very simple using promise.all.

GetURL in the previous example returns a Promise object that encapsulates the implementation of XHR communication. Pass an array of Promise objects that encapsulate the XHR communication to Promise.all, then the.then method will be called only after the whole XHR communication is FulFilled (becomes a pity or Rejected state).

promise-all-xhr.js
function getURL(URL) { return Promise(function (resolve, reject) { req = XMLHttpRequest(); req.open(, URL, ); req.onload = function () { (req.status === ) { resolve(req.responseText); } { reject( Error(req.statusText)); }}; req.onerror = function () { reject( Error(req.statusText)); }; req.send(); }); } request = { comment: function getComment() { return getURL(http://azu.github.io/promises-book/json/comment.json).then(JSON.parse); }, people: function getPeople() { return getURL(http://azu.github.io/promises-book/json/people.json).then(JSON.parse); }}; function () { return Promise.all([request.comment(), request.people()]); } // Run the example main().then(function (value) {console.log(value); }).catch(function(error){ console.log(error); });Copy the code

This example is executed the same way as the previous example. Promise.all differs from the previous example in the following respects.

  • The processing flow in Main is clear

  • Promise.all accepts an array of Promise objects as parameters

Promise.all([request.comment(), request.people()]);Copy the code

In the above code, request.ment () and request.people() start executing at the same time, and the result of each promise (the parameter value passed in resolve or reject), Is the same order as the Promise array passed to promise.all.

In other words, the. Then array of promises is executed in a fixed order, i.e. [comment, people].

main().then(function (results) { console.log(results); });Copy the code

If you use a timer to calculate the program execution time, as shown below, it’s pretty clear that the Promise array passed to promise.all starts executing at the same time.

promise-all-timer.js
Resolve function timerPromisefy(delay) {return Promise(function (resolve) {setTimeout(function ()) { resolve(delay); }, delay); }); } startDate = Date.now(); All ([timerPromisefy(), timerPromisefy(), timerPromisefy(), timerPromisefy()), timerPromisefy() ]).then(function (values) { console.log(Date.now() - startDate + ); / / about 128 ms console. The log (values); / /,32,64,128 [1]});Copy the code

TimerPromisefy will return a promise object once every certain period of time (specified by parameters). The state is FulFilled, and the state value is the parameter passed to timerPromisefy.

All is an array of these promises.

 promises = [
    timerPromisefy(),
    timerPromisefy(),
    timerPromisefy(),
    timerPromisefy()
];Copy the code

At this point, a promise occurs every 1, 32, 64, and 128 ms.

That is, it will take at least 128ms for all promises in the promise array to become resolve. In fact, if we calculate the execution time of promise.all, it does take 128ms.

As can be seen from the above results, the promises delivered to promise.all are not executed sequentially, but simultaneously and in parallel.

If all these promises were processed sequentially, they would have to wait 1ms → 32ms → 64ms → 128ms, which would take 225ms to complete.

To learn more about serial processing with promises, see the serial processing in Promises in Chapter 4.

2.9. Promise. Race

Next, let’s look at the promise.race method that handles multiple Promise objects, similar to promise.all.

It is used the same way as promise.all, which takes an array of Promise objects as arguments.

Promise. All will go on with the subsequent processing after all the objects received will become a pity or the Rejected state. This is a pity or Rejected state. Race will continue to process the Promise as long as one of the Promise objects enters the FulFilled or Rejected state.

Like the promise.all example, let’s look at an example of the use of promise.race with a timer.

promise-race-timer.js
Resolve function timerPromisefy(delay) {return Promise(function (resolve) {setTimeout(function ()) { resolve(delay); }, delay); }); } // The application stops running promise.race ([timerPromisefy(), timerPromisefy(), timerPromisefy(), timerPromisefy(), timerPromisefy() ]).then(function (value) { console.log(value); / / = > 1});Copy the code

The code above creates four Promise objects. These promise objects will become certain after 1ms, 32ms, 64ms and 128ms respectively, which is FulFilled. After the first 1ms becomes certain, the registered callback function will be called. The state promise object will call resolve(1), so the value passed to value will also be 1, and the console will print out 1.

This is a big pity. Let’s see if the next promise object will continue to run after the first promise object becomes a certain state.

promise-race-other.js
winnerPromise = Promise(function (resolve) { setTimeout(function () { console.log(this is winner); resolve(this is winner); }); }); loserPromise = Promise(function (resolve) { setTimeout(function () { console.log(this is loser); resolve(this is loser); }); }); Race ([winnerPromise, loserPromise]). Then (function (value) {console.log(value); // => 'this is winner' });Copy the code

We added console.log to the previous code to output debugging information.

If we execute the code above, we’ll see that the setTimeout methods of winnter and Loser Promise objects will complete and console.log will output their information, respectively.

That is, the Promise. Race will not cancel the implementation of other Promise objects after the first Promise object becomes a big pity.

在 ES6 PromisesThere is also no concept of canceling (breaking) the execution of a promise object, and we must ensure that the promise ends up in one of the resolve or Reject states. So Promise doesn’t applystateIt may be fixed. There are also libraries that provide cancellation of promises.

2.10. Then the or catch?

In the last chapter we said. Catch can also be understood. Then (undefined, onRejected).

In this book we will use.catch and.then separately for error handling.

We’ll also look at how using.then to specify a function that handles an error compares to using catch.

2.10.1. OnRejected cannot accept error handling

Let’s look at the code below.

then-throw-error.js
Function throwError(value) {// throwError(value); } // <1> onRejected () {return promise.resolve (). Then (throwError, onRejected); } // <2> Function goodMain(onRejected) {return Promise.resolve().then(throwError).catch(onRejected); } // Run an example badMain(function(){console.log(); }); goodMain(function(){ console.log(); });Copy the code

In the above code, badMain is a bad implementation (but not that bad), and goodMain is a very good error-handling version.

Why is badMain bad? Because although we specify the error handling function in the second parameter of.then, it can’t actually catch the errors that occur in the function (throwError in this case) specified by the first parameter ondepressing.

That is, even if throwError throws an exception, the onRejected function will not be called (i.e., it will not print “BAD”).

In contrast, goodMain’s code follows the throwError→onRejected call process. An exception in throwError will be caught by the next method in the chain,.catch, and handled accordingly.

The callback function specified by the onFulfilled parameter in the. Then method is actually for its Promise object or the previous promise object, rather than the first parameter specified in the. Then method, which is the object that onFulfilled refers to. This is why then and catch behave differently.

Both.then and.catch create and return a new Promise object. Promises don’t actually operate on exactly the same Promise object each time you add a handle to the method chain.

Figure 6. Then Catch flow

In this case, then is the processing of promise.resolve (42). An exception occurs in onFulfilled, and the onRejected specified in the same THEN method cannot catch the exception.

Exceptions that occur in this THEN can only be caught by the catch method that appears after the method chain.

Of course, since the.catch method is an alias for.then, we can do the same with.then. It’s just that using.catch is more intentional and easier to understand.

Promise.resolve().then(throwError).then(, onRejected);Copy the code

2.10.2. Summary

Here’s what we learned.

  1. This is a big pity. Use promise. Then (onFulfilled, onRejected)

    • This is a big onpity, which may happen unexpectedly in the onRejected.

  2. This is a big pity. Then (onFulfilled). Catch (onRejected)

    • Exceptions generated in then can be caught in.catch

  3. There is no essential difference between.then and.catch

    • Use it on separate occasions.

It is important to note that if the code looks like badMain, it is possible that the program will not behave as expected and therefore will not handle errors correctly.

3. Chapter.3 – Promise Test

In this chapter we’ll learn how to write test code for promises

3.1. Basic tests

We’ve already learned a little bit about ES6 Promises syntax, so I think you should be able to write Promise Demo code in real projects.

At this point, the next thing you need to worry about is how to write the Promise test code.

Let’s start by learning how to use Mocha[18] as a basic test for promises.

Just to be clear, all the tests in this chapter run in node.js.

The sample code that appears in this book also has corresponding test code. The test code is available for referenceazu/promises-book[19]

3.1.1. Mocha

Mocha is a testing framework tool under Node.js, and we won’t go into the details of Mocha[20] itself here. Readers interested in Mocha[21] can learn for themselves.

Mocha is free to choose any style from BDD, TDD, exports, and the Assert method used in the tests can also be combined with any other library. That is, Mocha itself only provides the framework for performing tests, leaving the rest up to the user to choose.

We chose to use Mocha here for three main reasons.

  • It is a well-known testing framework

  • Support for Node.js and browser-based tests

  • Support for “Promise testing”

Finally, as to why we support “Promise testing, “we’ll talk about that later.

To use Mocha in this chapter, we need to install Mocha through NPM.

$ npm install -g mochaCopy the code

In addition, we use node.js’s built-in Assert module, so no additional installation is required.

First, let’s try writing code that tests a traditional callback-style asynchronous function.

3.1.2. Callback function style test

If you want to test an asynchronous processing using the callback function style, the code with Mocha looks like this.

basic-test.js
assert = require(power-assert); describe(Basic Test, function () { context(When Callback(high-order function), function () { it(should use `done` for test, function (done) { setTimeout(function () { assert(); done(); }); }); }); context(When promise object, function () { it(should use `done` for test? , function (done) { promise = Promise.resolve(); / / こ の テ ス ト コ ー ド は あ る owe 陥 が あ り ま す promise. Then (function (value) {assert (value = = =); done(); }); }); }); });Copy the code

Save this code as basic-test.js, and you can test it using the command-line tools of Mocha you just installed.

$ mocha basic-test.jsCopy the code

Mocha’s IT method specifies the done argument, which waits until the done() function is executed so that asynchronous processing can be tested.

Asynchronous tests in Mocha will follow these steps.

it(should use `done` for test, function (done) {
    (1)
    setTimeout(function () {
        assert();
        done();(2)}); });Copy the code

1 Callback asynchronous processing
2 calldoneEnd of post-test

This is also a very common implementation.

3.1.3. UsedoneThe Promise of the test

Next, let’s look at how to use done for the Promise test.

it(should use `done` for test? , function (done) { promise = Promise.resolve();(1)
    promise.then(function (value) {
        assert(value === );
        done();(2)
    });
});Copy the code

1 Create aFulfilledThe promise of object
2 calldoneEnd of post-test

Promise. Resolve is used to return the Promise object, which will be FulFilled. Finally, the callback function set through.then is also called.

Columns like: Can promises only operate asynchronously? As already mentioned, promise objects are always called asynchronously, so tests need to be written as asynchronous calls as well.

However, in the previous test code, the problem occurred when assert failed.

Test for exception promises
it(should use `done` for test? , function (done) { promise = Promise.resolve(); promise.then(function (value) { assert(false); // => throw AssertionError done(); }); });Copy the code

In this test assert failed, so you might think you should throw a “test failed” error, when in fact the test doesn’t end until the timeout.

Figure 7. Since the test does not end, it is suspended until an unknown timeout occurs.

Typically, when an assert fails, it throws an error that the testing framework catches to determine the test failure.

However, in the case of a Promise, any errors that occur during the execution of the.then bound function will be caught by the Promise, and the testing framework will be unaware of the error.

Let’s improve the assert failed promise test so that it handles the test results when an Assert fails.

An example of a normal test failure
it(should use `done` for test? , function (done) { promise = Promise.resolve(); promise.then(function (value) { assert(false); }).then(done, done); });Copy the code

In the example above where the test failed properly, we added.then(done, done) at the end to make sure done is always called; Statements.

Done () is called when an Assert test passes (on success) and done(error) is called when an Assert test fails.

Thus, we wrote the same Promise test as the callback function-style test.

However, to handle assert failures, we need to add.then(done, done); The code. This requires us to be very careful when writing the Promise test, because if we forget to include the above statement, we could end up writing test code that never returns until the timeout.

In the next section, let’s take a look at what mechanisms support the “Promises test “in the original justification for using Mocha.

3.2. Mocha’s support for Promise

Here, we’ll learn what Mocha supports “testing for Promises.”

The official Asynchronous Code site also records an outline of the Promise test.

Alternately, instead of using the done() callback, you can return a promise. This is useful if the APIs you are testing return promises instead of taking callbacks:

Instead of using callback style code like done() when testing promises, return a Promise object.

So what will the code actually look like? So let’s look at a concrete example that should make sense.

mocha-promise-test.js
 assert = require(power-assert);
describe(Promise Test, function () {
    it(should return a promise object, function () {
         promise = Promise.resolve();
        return promise.then(function (value) {
            assert(value === );
        });
    });
});Copy the code

This code rewrites the previous done example as Mocha’s Promise test.

The modifications are mainly made in the following two points:

  • Removed the done

  • Return the result as a Promise object

Written this way, when an assert fails, the test itself will fail.

it(should be fail, function () { return Promise.resolve().then(function () { assert(false); // => Test failed}); });Copy the code

Then (done, done); then(done, done); Such code is not directly related to the test logic per se.

Mocha has support for Promises testing | Web scratch [22] also mentioned in this article (Japanese) on Mocha to Promise the support of the test.

3.2.1. Unexpected (failed) test results

Since Mocha provided tests for promises, we thought it would be better to write according to Mocha’s rules. But this code can lead to unexpected exceptions.

Example: the test code for the mayBeRejected() function below, which returns a promise object that becomes Rejected when they are accepted.

You want to test the Error Object
function mayBeRejected(){ (1)
    return Promise.reject( Error());
}
it(is bad pattern, function () {
    return mayBeRejected().catch(function (error) {
        assert(error.message === );
    });
});Copy the code

1 This function is used to test the returned Promise object

The purpose of this test includes the following two points:

mayBeRejected()The return promise object becomes a FulFilled state

The test will fail

mayBeRejected()Return a Promise object with the Rejected state

Check the Error object in Assert

The above test code will call the function registered in onRejected when the promise object changes to Rejected, thus not fulfilling the promise processing flow and the test will succeed.

The problem with this test code is that the test will be consistently successful when mayBeRejected() returns a promise object that is a depressing state.

function mayBeRejected(){ (1)
    return Promise.resolve();
}
it(is bad pattern, function () {
    return mayBeRejected().catch(function (error) {
        assert(error.message === );
    });
});Copy the code

1 The returned promise object will become a pity

In this case, since the onRejected function registered in catch is not called, assert will not be executed and the test will always pass (passed, success).

To solve this problem, we can add a.then call before a.catch, which means that if a.then is called, the test needs to fail.

function failTest() { (1)
    throw  Error(Expected promise to be rejected but it was fulfilled);
}
function mayBeRejected(){
    return Promise.resolve();
}
it(should bad pattern, function () {
    return mayBeRejected().then(failTest).catch(function (error) {
        assert.deepEqual(error.message === );
    });
});Copy the code

1 Fail the test by throwing

But then or catch? Exceptions thrown by failTest are caught by a catch.

Figure 8. Then Catch flow

The execution flow of the program is then → catch, and the Error object passed to the catch is of type AssertionError, which is not what we want.

That is to say, we hope the test can only pass the Promise object whose state will change to onRejected. If the promise object is onFulfilled, then the test will always pass.

3.2.2. Clarify the two states and improve the unexpected (abnormal) conditions in the test

When writing an example of testing against Error objects, how do you weed out cases where the test will pass by accident?

The easiest way to do this is to determine in your test code what to do in the states of various Promise objects.

This will become a depressing state

Tests are expected to fail

The Rejected state

Test with Assert

That is, we need to explicitly specify in the test code what processing needs to happen in the Fulfilled and Rejected states.

function mayBeRejected() { return Promise.resolve(); } it(catch -> then, function () {// this will be a big pity when they are jected(). Then (failTest, jected) function (error) { assert(error.message === ); }); });Copy the code

This way, you can write the test code that fails when the promise becomes a big pity.

Figure 9. Promise onRejected test

Then in the or catch? We have already said that, in order to avoid missing the error processing, we recommend using then → catch instead of using the call form with two parameters like. Then (ondepressing, onRejected).

But When it came time to write the test code, Promise’s powerful error-handling mechanism was a hinderance. So we use. Then (failTest, onRejected), which specifies exactly what the promise should do in each state.

3.2.3. The summary

In this section, we’ve covered some of the unexpected things that can happen when you use Mocha for Promise testing.

  • Normal code is easier to understand if it follows the then → catch process

    • This is for error handling convenience. See then or Catch?

  • Centralize the test code to then for processing

    • To be able to pass the AssertionError object to the test framework.

By writing. Then (onFulfilled, onRejected), we can explicitly specify what to do when the promise object becomes Fulfilled or Rejected.

However, code like the one below always looks a bit less intuitive due to the test processing of Rejected when you need to display it.

Promise.then (failTest, function(error){// Use assert to test error});Copy the code

In the next section, we’ll show you how to write helper functions to make it easier to write Promise tests, and how to write tests that are easier to understand.

3.3. Write controllable tests (Controllable tests)

Before moving on, let’s define what controlled testing is. Here we define controlled testing as follows.

The promise object to test

  • If writing a test that is expected to be a depressing state

    • Don’t Fail if you have Rejected

    • Assertion Fail when assertions are inconsistent

  • If you expect the Rejected state

    • The result is a pity test and Fail

    • Assertion Fail when assertions are inconsistent

If a test can capture the use Case (Fail) item above, it is called controlled testing.

That is, a test case should include the following tests.

  • This is a pity or Rejected

  • Check the value passed to the assertion

The previous code that uses.then is a test whose expected result is Rejected.

Promise. then(failTest, function(error){// Use assert to verify the error object assert(error instanceof error); });Copy the code

3.3.1. The converted state must be explicitly specified

To write effective test code, we need to explicitly specify the state of the promise as either Depressing or Rejected.

But because.then can be called without parameters, it is sometimes possible to forget to add conditions that make the test fail.

Therefore, we can define a helper function that explicitly defines the expected state of a promise.

The author created a library azU/Promise-test-Helper [23] to make it easy to test promises, and I use a shortened version of this library in this article.

First we create a shouldRejected Helper function named for the example where they expect the test to return an onRejected status in the.then example.

shouldRejected-test.js
assert = require(power-assert); function shouldRejected(promise) { return { catch: function (fn) { return promise.then(function () { throw Error(Expected promise to be rejected but it was fulfilled); }, function (reason) { fn.call(promise, reason); }); }}; } it(should be rejected, function () { promise = Promise.reject( Error(human error)); return shouldRejected(promise).catch(function (error) { assert(error.message === human error); }); });Copy the code

The shouldRejected function accepts a promise object as an argument and returns an object with the catch method.

You can use the same code in the Catch as in onRejected, so you can use the assertion method in the catch.

Outside shouldRejected, these are codes similar to the following and treated much the same as normal promise.

  1. Pass the promise object to be tested to the shouldRejected method

  2. Write code that handles onRejected in the catch method of the returned object

  3. Assertion in onRejected

When shouldRejected function is used, if the depressing is called, an exception will be thrown and the test will fail.

promise.then(failTest, function(error){ assert(error.message === human error); }); ShouldRejected (promise). Catch (function (error) {assert(error. Message === human error); });Copy the code

The test code will also be intuitive using a shouldRejected helper function like this.

Figure 10. Promise onRejected test

As above, we can also write a shouldFulfilled helper function that tests the promise object and expects the result to be Fulfilled.

shouldFulfilled-test.js
assert = require(power-assert); function shouldFulfilled(promise) { return { : function (fn) { return promise.then(function (value) { fn.call(promise, value); }, function (reason) { throw reason; }); }}; } it(should be fulfilled, function () { promise = Promise.resolve(value); return shouldFulfilled(promise).then(function (value) { assert(value === value); }); });Copy the code

This is essentially the same construction as shouldRejected-test.js above, except that the catch method returning the object is then, promise.then the two arguments are switched.

Nodule 3.3.2 rainfall distribution on 10-12.

In this section, you learned how to write test code for Promise specific states and how to use testable helper functions.

Shoulddepressing and shouldRejected we used here can also be found in the class library below.

Azu/promise – test – helper [24].

In addition, the helper methods in this section all presuppose Mocha’s support for Promises, which can be cumbersome to use in done-based testing.

It’s up to everyone to choose between using the Promis support based on the testing framework or using a callback style like Done. It’s just a matter of style, and I don’t think it’s necessary to argue over which is better.

For example, when testing under CoffeeScript[25], it might be easier to understand done because CoffeeScript implicitly uses return.

Testing promises is more difficult than testing asynchronous functions in general, and while it’s up to you to decide which way to test, it’s important to test consistently within the same project.

4. Chapter.4 – Advanced

In this chapter, we’ll take a closer look at some of the advanced content of promises, based on what we’ve learned before, to deepen our understanding of promises.

4.1. Promise Implementation Class Library

In this section, we will not attempt to explain browser implemented promises, but rather introduce some third party implemented Promise compatible libraries.

4.1.1. Why are these libraries needed?

Why do you need these libraries? I’m sure some readers have this question. The first reason that comes to mind is that some runtime environments do not support ES6 Promises.

One of the first things to consider when looking for Promises on the Web was Promises/A+ compatibility.

Promises/A+ is the predecessor of ES6 Promises, and Promises’ THEN also comes from this community-based specification.

Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+

Promises/A+ is really just A specification for Promise#then, so some libraries might implement other things like All or Catch with A different name.

If we say that a library is then compatible, we actually mean Thenable, which transforms Promise objects based on ES6 promises by using promise.resolve.

The rules in ES6 Promises about Promise objects include no errors when using catch methods, or when using promise.all.

4.1.2. Polyfill and extended libraries

In these Promise implementation libraries, we focus on two types of libraries here.

One is called Polyfill, and the other is Promises/A+ compatibility, but adds its own unique features.

Promise’s implementation libraries are numerous, and we’ve covered only a few of them here.
Polyfill

All you need to do is load the Polyfill class library into your browser and use the promised methods using IE10 or other browsers that don’t yet offer Promise support.

That is, if you load the Polyfill class library, you can run the sample code in this article in an environment that doesn’t yet support Promise.

jakearchibald/es6-promise[26]

A Polyfill library compatible with ES6 Promises. It is based on rsvp.js [27], A library that is compatible with Promises/A+. It is A subset of Rsvp.js and only implements Promises’ apis.

yahoo/ypromise[28]

This is a standalone version of YUI[29] ‘s Promise Polyfill, with compatibility with ES6 Promises. The sample code in this book is also based on the YPromise Polyfill to run online.

getify/native-promise-only[30]

The library is designed to be polyfill for ES6 Promises. It is designed strictly according to the ES6 Promises specification and does not add features not defined in the specification. Native Promise support is preferred if the runtime environment has native Promise support.

Promise extends the class library

In addition to implementing the specifications defined in Promise, the Promise extension library adds functionality that it defines itself.

The Promise extension libraries are numerous, and we’ll cover only two of the more well-known ones.

kriskowal/q[31]

The Q library Promises Promises and Deferreds. It has been under development since 2009 and also provides node.js file IO API Q-io [32], etc. It is a class library that can be used in many scenarios.

petkaantonov/bluebird[33]

In addition to the compatibility with the Promise specification, the library also extends the rich functions of canceling the running of Promise objects, obtaining the progress of Promise running, and extending detection of error handling. In addition, it also makes great efforts in the implementation of performance problems.

Q and Bluebird can both run in the browser, and their API reference is also their feature.

  • Kriskowal/Q Wiki[34]

Q and other documents explain in detail the similarities and differences between Q Deferred and jQuery Deferred, as well as how to migrate Coming from jQuery[35].

  • Bluebird/Api. md at Master · Petkaantonov/Bluebird[36]

In addition to providing rich implementation methods for using Promise, Bluebird documents also involve corresponding methods when errors occur and anti-patterns in Promise [37].

The documentation for both libraries is friendly, and even if we don’t use them, it’s useful to read their documentation.

4.1.3. To summarize

This section introduces Polyfill and extension libraries in Promise’s implementation library.

Promise’s implementation libraries are numerous, and it’s up to you to choose which one to use.

However, because these libraries implement Promises that have Promises/A+ or ES6 Promises common interfaces, you can sometimes refer to code or extensions from other libraries when using one of these libraries.

One of the goals of this book is to become familiar with the common concepts in Promise and to become comfortable with these technologies in practice.

4.2. Promise. Resolve and Thenable

Resolve, as we discussed in Chapter 2, is that one of the biggest features of Promise.resolve is that you can convert thenable’s objects into Promise objects.

In this section, you’ll learn what you can do with the ability to convert a Thenable object to a Promise object.

4.2.1. Convert Web Notifications to thenable objects

Here we use the desktop notification API Web Notifications[38] as an example.

Detailed information about the Web Notifications API can be found at the Web site below.

Simply put, the Web Notifications API displays Notification messages via new Notification as shown in the following code.

 Notification();Copy the code

Of course, in order to display Notification messages, we need to get permission from the user before running new Notification.

Figure 11. Confirm whether Notification is allowed

The result of the user selecting the allow Notification dialog is passed to our application through notification. permission, which can be either granted or denied.

The option of whether to allow Notification dialog box has been added in Firefox in addition to allow and denypermanentValid within the session scopeTwo additional options, of courseNotification.permissionThe values of theta are the same.

In the program can pass Notification. RequestPermission () to pop up whether to allow Notification dialog box, the result of the user to select the status parameter to the callback function.

We can also see from this callback that the user’s choice to allow or deny notifications is asynchronous.

Notification. RequestPermission (function (status) {/ / the status value is "granted" or "denied" is the console. The log (status); });Copy the code

The process is as follows until the user receives and displays the notification.

  • Displays a dialog box to allow notification and asynchronously processes the result of the user’s selection

  • If the user allows it, the Notification message is displayed through new Notification. There are two cases

    • The user has allowed this before

    • A dialog box is displayed asking you whether to allow desktop notification

  • If the user does not allow the operation, no operation is performed

Although there are several scenarios mentioned above, the end result is user approval or rejection, which can be summarized in the following two modes.

Allow (” granted “)

Use new Notification to create Notification messages

Rejection (” denied “)

There is no operation

Do you feel like you’ve seen these two modes before? Ha ha, the user’s choice result is very similar to the Promise object which becomes a pity or Rejected state in the Promise.

Resolve (successful) == user permission (“granted”)

Call the ondepressing method

Reject == User reject(“denied”)

Call the onRejected function

Can we code desktop notifications as promises? Let’s start with callback-style code to see how it works.

4.2.2. Web Notification Wrapper Function

First, let’s rewrite the Web Notification API wrapper function above with back to function style code, as shown below.

notification-callback.js
function notifyMessage(message, options, callback) { (Notification && Notification.permission === granted) { notification = Notification(message, options); callback(, notification); } (Notification.requestPermission) { Notification.requestPermission(function (status) { (Notification.permission ! == status) { Notification.permission = status; } (status === granted) { notification = Notification(message, options); callback(, notification); } { callback( Error(user denied)); }}); } { callback( Error(doesnt support Notification API)); // The second argument is the option object passed to 'Notification' notifyMessage(, {}, function (error, notification) { (error){ return console.error(error); } console.log(notification); // Notify object});Copy the code

In callback-style code, error is set when the user refuses to receive a notification, and notification is displayed and set if the user agrees to receive a notification.

The callback function accepts both error and notification
function callback(error, notification){

}Copy the code

Next, I want to rewrite this callback function style code again using Promise.

4.2.3. Web Notification as Promise

Based on the above callback style notifyMessage function, let’s create a notifyMessageAsPromise method that returns a Promise object.

notification-as-promise.js
function notifyMessage(message, options, callback) { (Notification && Notification.permission === granted) { notification = Notification(message, options); callback(, notification); } (Notification.requestPermission) { Notification.requestPermission(function (status) { (Notification.permission ! == status) { Notification.permission = status; } (status === granted) { notification = Notification(message, options); callback(, notification); } { callback( Error(user denied)); }}); } { callback( Error(doesnt support Notification API)); } } function notifyMessageAsPromise(message, options) { return Promise(function (resolve, reject) { notifyMessage(message, options, function (error, notification) { (error) { reject(error); } { resolve(notification); }}); }); } // Run the example notifyMessageAsPromise(). Then (function (notification) {console.log(notification); }). Catch (function(error){console.error(error); });Copy the code

When the user is allowed to receive notifications, running the code above will display “Hi!” The message.

The.then function is called when the user receives a notification message, and the.catch method is called when the user rejects the message.

Since the browser saves the license status of the Web Notifications API on a website basis, there are actually four modes that exist.

User license has been obtained

The.then method is called

A dialog box is displayed asking for permission

The.then method is called

The status is rejected by the user

The.catch method is called

A query dialog box is displayed and rejected by the user

The.catch method is called

That is, if the native Web Notifications API is used, all four cases need to be handled in the application, which can be simplified into two cases for ease of handling, as shown in the wrapper function below.

The above notification-as-promise.js may seem convenient, but it may not work in an environment that does not support promises.

If you want to write a promise-style library like notification-as-Promise.js, I think you have some options.

The environment that supports Promise is the premise
  • End users are required to guarantee support for promises

  • Does not work (that is, should fail) in an environment that does not support promises.

Implemented in the class library
Promise
  • Implement the Promise functionality in the class library

  • For example) localForage [39]

It should also be available in callback functions
Promise
  • Users can choose the appropriate way to use it

  • Return type Thenable

Notification-as-promise. js is written on the premise that a promise exists.

Back to the body, Thenable is here to help implement the concept of using Promise in callback functions as well.

4.2.4. Web Notifications As Thenable

We’ve already said that thenable is an object with a.then method. Let’s add a method to notification-callback.js that returns type thenable.

notification-thenable.js
function notifyMessage(message, options, callback) { (Notification && Notification.permission === granted) { notification = Notification(message, options); callback(, notification); } (Notification.requestPermission) { Notification.requestPermission(function (status) { (Notification.permission ! == status) { Notification.permission = status; } (status === granted) { notification = Notification(message, options); callback(, notification); } { callback( Error(user denied)); }}); } { callback( Error(doesnt support Notification API)); }} // Return 'thenable' function notifyMessageAsThenable(message, options) {return {: function (resolve, reject) { notifyMessage(message, options, function (error, notification) { (error) { reject(error); } { resolve(notification); }}); }}; Resolve (notifyMessageAsThenable(message)). Then (function (notification) {console.log(notification);} // Run the example promise.resolve (notifyMessageAsThenable(message)). }). Catch (function(error){console.error(error); });Copy the code

NotifyMessageAsThenable has been added to notification-thenable.js. This method returns an object that has a THEN method.

The then method takes the same arguments as new Promise(function (resolve, reject){}), which executes resolve when it is certain and calls reject when it is rejected.

The then method does the same thing as the notifyMessageAsPromise method in notification-as-Promise.js.

Resolve (thenable) We can use the Promise feature by using the Thenable Promise object.

Promise.resolve(notifyMessageAsThenable(message)).then(function (notification) { console.log(notification); }). Catch (function(error){console.error(error); });Copy the code

  • The class library side does not provide an implementation of Promise

    • The user implements the Promise themselves via promise.resolve (thenable)

  • Resolve (thenable) is used in conjunction with promise.resolve (thenable)

By using Thenable objects, we can implement an implementation style similar to the existing callback style and Promise style.

4.2.5. Summary

In this section we learned what Thenable is and how to use Thenable as a Promise object through promise.resolve (Thenable).

Callback — Thenable — Promise

The Thenable style appears to be somewhere between the callback and the Promise style, and as a public API for class libraries is a bit immature, so it’s not very common.

Thenable itself does not rely on the Promise feature, but there is no way to use Thenable outside of Promise, so Thenable can be considered to be indirectly dependent on Promise.

In addition, users need to have an understanding of promise.resolve (thenable) to use Thenable well, so part of the public API as a class library can be difficult. Thenable is used internally more often than in public apis.

When writing libraries for asynchronous processing, it is recommended to write callback style functions first and then convert to public apis.

The Core Module of Node.js looks like this approach. In addition to the basic callback style functions provided by the class library, users can also implement them through promises or generators or other methods that they are good at.

In cases where the library is originally intended to be used by Promises, or itself depends on Promises, I think there should be no problem with having a function that returns a Promise object as a public API.

When should YOU use Thenable?

When should you use Thenable?

Probably the most likely use is to convert between Promise libraries.

For example, the Promise instance of the library Q is the Q Promise object, which provides methods that ES6 Promises Promise objects do not. The Q Promise object provides methods like promise.finally(callback) and promise.nodeify(callback).

If you want to change Promises from ES6 Promises to Q Promises, Thenable is on its way.

Use thenable to convert the Promise object into a Q Promise object
Q = require(); Promise = promise (function(resolve){resolve(); }); Then (function(value){console.log(value); }).finally(function(){(1)
    console.log(finally);
});Copy the code

1 You can use it because it’s a Q Promise objectfinallymethods

The promise object originally created in the code above has the then method and is therefore a Thenable object. We can convert this Thenable object into a Q Promise object using the Q(thEnable) method.

It has the same mechanism as promise.resolve (thenable) and vice versa.

In this way, although the Promise class libraries have their own types of Promise objects, they can convert Promise objects between the class libraries (including native promises of course) through the common concept of Thenable.

As we can see above, Thenable is mostly used inside the library implementation, so you won’t see Thenable used very often from the outside. But it’s important to keep in mind that Thenable is a very important concept in Promise.

4.3. Use reject instead of throw

The Promise constructor, as well as the functions executed by then calls, can basically be thought of as being in a try… In a block of catch code, so the program itself does not terminate because of an exception even if a throw is used in such code.

If you use a throw statement in a Promise, you’ll get a try… Finally, the promise object changes to the Rejected state.

promise = Promise(function(resolve, reject){ throw Error(message); }); promise.catch(function(error){ console.error(error); // => "message" });Copy the code

The code doesn’t have a problem running like this, but it makes more sense to use reject if you want to set the Promise to Rejected.

So the above code can be rewritten as follows.

promise = Promise(function(resolve, reject){ reject( Error(message)); }); promise.catch(function(error){ console.error(error); // => "message" })Copy the code

We can also think of it this way: instead of calling throw, we call Reject, so it makes sense to pass reject an object of type Error.

4.3.1. What are the advantages of using reject?

Then again, why should YOU use reject instead of throw when you want to set the state of a Promise object to Rejected?

First of all, it’s hard to tell whether a throw is something we throw on our own initiative, or something else that actually causes it.

For example, when using Chrome, Chrome developer tools provides the ability to automatically break the debugger when an exception occurs in the application.

Figure 12. Pause On Caught Exceptions

When we turn this function on, the debugger’s break behavior is triggered when we execute the throw in the code below.

 promise =  Promise(function(resolve, reject){
    throw  Error(message);
});Copy the code

This has nothing to do with debugging, and because the throw in the Promise was broken, it seriously affected the functionality provided by the browser.

Reject in THEN

In the Promise constructor, there is a parameter that specifies the Reject method, and it is easy to use this parameter to set the state of the Promise object to Rejected instead of relying on throw.

What do you do if you want to do reject in THEN, as follows?

promise = Promise.resolve(); Promise.then (function (value) {setTimeout(function () {reject ()},); 1 somethingHardWork(); }). Catch (function (error) {// timeout error - 3});Copy the code

What if we call the reject method in then, but only the promise object is passed to the current callback?

See how you can implement timeout handling using promisesCancel the XHR request using promise.race and delayIn.

Here’s a reminder of how THEN works.

Callbacks registered in THEN can return a value that is passed to subsequent callbacks in then or catch.

Moreover, the return value type of return is not only simple literals, but also complex object types, such as promise objects.

This is a big pity. If the promise object is returned, then according to the state of the Promise object, which of the onFulfilled and onRejected callback functions registered in the next THEN will be called can be determined.

promise = Promise.resolve(); promise.then(function () { retPromise = Promise(function (resolve, // The state of resolve or reject determines which method will be called ondepressing or onRejected}); return retPromise;(1)
}).then(onFulfilled, onRejected);Copy the code

1 The subsequent then callbacks are determined by the state of the Promise object

In other words, the retPromise object in the Rejected state calls the onRejected method of the then (reject), thus making it possible to reject (reject) even if the THEN throws are not used.

 onRejected = console.error.bind(console);
 promise = Promise.resolve();
promise.then(function () {
     retPromise =  Promise(function (resolve, reject) {
       reject( Error(this promise is rejected));
    });
    return retPromise;
}).catch(onRejected);Copy the code

Use Promise. Reject to simplify the code even further.

 onRejected = console.error.bind(console);
 promise = Promise.resolve();
promise.then(function () {
    return Promise.reject( Error(this promise is rejected));
}).catch(onRejected);Copy the code

4.3.3. Summary

In this section we have mainly studied

  • Reject is safer than throw

  • Use the reject method in then

We probably don’t use reject much in practice, but there are many advantages to using reject over throwing without thinking.

For a more detailed example of this, see the section on cancelling XHR requests using Promise.race and Delay.

4.4. The Deferred and Promise

In this section we’ll take a look at the relationship between Deferred and Promise

4.4.1. What is Deferred?

Speaking of promises, I’m sure you’ve also heard the term Deferred. JSDeferred[41], JSDeferred[40], JSDeferred[40], JSDeferred[41], JSDeferred[40], JSDeferred[41], JSDeferred[40], JSDeferred[41]

Unlike promises, there is no common specification for Deferred applications, and each Library implements them according to its own preferences.

Here, we intend to center on a similar implementation of jquery.Deferred [42].

4.4.2. Relationship between Deferred and Promise

Simply put, Deferred and Promise have the following relationship.

  • Deferred have Promise

  • Deferred has privileged methods to operate on the state of the Promise (” Special Ah10s ソ and Initiate mode “in the figure)

Figure 13. Deferred and Promise

I think it should be easy for you to understand that Deferred and Promise are not in competition, but rather that Deferred implies Promise.

This is a simplified version of the jquery.deferred structure. Of course, there are Deferred implementations that do not contain promises.

It may be hard to understand just looking at the picture, but let’s look at how to implement a Deferred through a Promise.

4.4.3. Deferred top on Promise

An example of implementing a Deferred based on a Promise.

deferred.js
function Deferred() {
    .promise =  Promise(function (resolve, reject) {
        ._resolve = resolve;
        ._reject = reject;
    }.bind());
}
Deferred.prototype.resolve = function (value) {
    ._resolve.call(.promise, value);
};
Deferred.prototype.reject = function (reason) {
    ._reject.call(.promise, reason);
};Copy the code

Let’s rewrite getURL, which we implemented with Promise, with Deferred.

xhr-deferred.js
function Deferred() { .promise = Promise(function (resolve, reject) { ._resolve = resolve; ._reject = reject; }.bind()); } Deferred.prototype.resolve = function (value) { ._resolve.call(.promise, value); }; Deferred.prototype.reject = function (reason) { ._reject.call(.promise, reason); }; function getURL(URL) { deferred = Deferred(); req = XMLHttpRequest(); req.open(, URL, ); req.onload = function () { (req.status === ) { deferred.resolve(req.responseText); } { deferred.reject( Error(req.statusText)); }}; req.onerror = function () { deferred.reject( Error(req.statusText)); }; req.send(); return deferred.promise; } // Run the sample URL = http://httpbin.org/get; getURL(URL).then(function onFulfilled(value){ console.log(value); }).catch(console.error.bind(console));Copy the code

Privileged methods that can operate on the Promise state refer to methods that can call resolve, reject, etc., on the state of the Promise object, whereas normal promises can only operate on the state of the Promise object within the method passed through the constructor.

Let’s look at the implementation differences between Deferred and Promise.

xhr-promise.js
function getURL(URL) { return Promise(function (resolve, reject) { req = XMLHttpRequest(); req.open(, URL, ); req.onload = function () { (req.status === ) { resolve(req.responseText); } { reject( Error(req.statusText)); }}; req.onerror = function () { reject( Error(req.statusText)); }; req.send(); }); } // Run the sample URL = http://httpbin.org/get; getURL(URL).then(function onFulfilled(value){ console.log(value); }).catch(console.error.bind(console));Copy the code

Comparing the two versions of getURL, we see the following differences.

  • Deferred does not require enclosing the code in promises

    • Because it is not nested in a function, you can reduce the indentation by one layer

    • In turn, there is no error handling logic in Promise

In the following respects, they do the same.

  • Overall processing process

    • When to call resolve, reject

  • The functions all return promise objects

Because Deferred includes promises, the general flow is similar, but Deferred has privileged methods for working with promises and a high degree of freedom to customize the flow control.

In promises, for example, the main processing logic is written in the constructor, and the timing of the resolve and reject calls is pretty determinate.

Promise(function (resolve, reject){// make a Promise object state});Copy the code

With Deferred, you don’t need to write a block of code for the processing logic. You just create a Deferred object that can be called resolve or Reject at any time.

deferred = Deferred(); The 'resolve' and 'reject' methods can be called at willCopy the code

We simply implemented a Deferred above, and I think you can see the difference between this and a Promise.

If promises are used to abstract values and Deferred objects to abstract states or operations that have not yet finished processing, we can understand the difference between the two.

In other words, Promise represents an object. The state of this object is uncertain now, but at a certain point in the future, its state will either become normal or Rejected. This is a big pity. The Deferred object represents the fact that a processing has not yet finished, and when its processing has finished, a Promise can be used to get the result.

If you want to learn more about Deferred, you can refer to the resources below.

Deferred was originally introduced as a concept in Python’s Twisted[43] framework. In the JavaScript world it can be thought of as being introduced by libraries such as Mochikit.async [44], Dojo /Deferred[45], etc.

4.5. Cancel the XHR request using promise.race and delay

In this section, as a concrete example of the promise.race we learned in Chapter 2, we look at how to use promise.race to implement the timeout mechanism.

Of course, XHR has a timeout[46] property, which can also be used to implement a simple timeout function, but in order to support multiple XHR timeouts and other functions at the same time, we used an easy to understand asynchronous way to cancel an ongoing operation by timeout in XHR.

4.5.1. Make a Promise wait for a specified time

First, let’s look at how to implement timeouts in promises.

A timeout is an operation that takes place after a certain amount of time has elapsed.

Let’s start with a simple function that calls setTimeout in a Promise.

delayPromise.js
function delayPromise(ms) {
    return  Promise(function (resolve) {
        setTimeout(resolve, ms);
    });
}Copy the code

DelayPromise (ms) returns an onFulfilled Promise object after the specified number of milliseconds. This is only coded slightly differently than using the setTimeout function directly, as shown below.

SetTimeout (function () {alert(after 100ms!) ; }); // == almost the same operation delayPromise(). Then (function () {alert(after 100ms!) ; });Copy the code

The concept of promise objects is very important to keep in mind here.

Timeouts in 4.5.2. Promise.race

Let’s review the static method promise.race, which is used to continue processing once any Promise object enters a determined (resolved) state, as shown in the following example.

winnerPromise = Promise(function (resolve) { setTimeout(function () { console.log(this is winner); resolve(this is winner); }); }); loserPromise = Promise(function (resolve) { setTimeout(function () { console.log(this is loser); resolve(this is loser); }); }); Race ([winnerPromise, loserPromise]). Then (function (value) {console.log(value); // => 'this is winner' });Copy the code

We can implement a simple timeout mechanism by putting the delayPromise we just mentioned in the promise.race along with other promise objects.

simple-timeout-promise.js
function delayPromise(ms) {
    return  Promise(function (resolve) {
        setTimeout(resolve, ms);
    });
}
function timeoutPromise(promise, ms) {
     timeout = delayPromise(ms).then(function () {
            throw  Error(Operation timed out after  + ms + );
        });
    return Promise.race([promise, timeout]);
}Copy the code

The function timeoutPromise(comparison promise, MS) takes two arguments, the first a promise object that needs to use a timeout mechanism, and the second a timeout, which returns a competing promise object created by promise.race.

We can then use timeoutPromise to write code with a timeout mechanism like the one below.

function delayPromise(ms) { return Promise(function (resolve) { setTimeout(resolve, ms); }); } function timeoutPromise(promise, ms) { timeout = delayPromise(ms).then(function () { throw Error(Operation timed out after + ms + ); }); return Promise.race([promise, timeout]); } // taskPromise = Promise(function(resolve){delay = math.random () *; setTimeout(function(){ resolve(delay + ); }, delay); }); TimeoutPromise (taskPromise,). Then (function(value){console.log(taskPromise, + value); }).catch(function(error){ console.log(, error); });Copy the code

Although an exception is thrown when a timeout occurs, it is impossible to tell whether the exception is a normal error or a timeout error.

To distinguish the Error object type, we’ll subclass TimeoutError.

4.5.3. Customizing Error objects

The Error object is a build in object built into ECMAScript.

We can’t perfectly create a class that inherits from Error due to stack trace and so on, but our goal here is just to differentiate ourselves from Error, and we’ll create a TimeoutError class to do that.

In ECMAScript6, you can use the class syntax to define inheritance relationships between classes.

Class MyError extends Error{// Extends Error}Copy the code

In order for our TimeoutError to support methods like Error Instanceof TimeoutError, we need to do the following.

TimeoutError.js
function copyOwnFrom(target, source) {
    Object.getOwnPropertyNames(source).forEach(function (propName) {
        Object.defineProperty(target, propName, Object.getOwnPropertyDescriptor(source, propName));
    });
    return target;
}
function TimeoutError() {
     superInstance = Error.apply(, arguments);
    copyOwnFrom(, superInstance);
}
TimeoutError.prototype = Object.create(Error.prototype);
TimeoutError.prototype.constructor = TimeoutError;Copy the code

We define the TimeoutError class and constructor, which inherits Error’s prototype.

It is used in the same way as a normal Error object, using a throw statement, as shown below.

promise = Promise(function(){ throw TimeoutError(timeout); }); promise.catch(function(error){ console.log(error instanceof TimeoutError); // true });Copy the code

With this TimeoutError object, it’s easy to distinguish between catching errors that are due to timeouts and errors that are caused by something else.

Chapter 28. Subclassing Built-in objects [47] explains how to inherit JavaScript built-in objects in this Chapter. In addition the Error – JavaScript | MDN [48] also has carried on the detailed according to the Error object.

4.5.4. Cancel the XHR operation by timeout

At this point, I assume you have some idea of how to use Promise to cancel an XHR request.

Canceling the XHR operation itself is not that difficult; just call abort() on the XMLHttpRequest object.

To call abort() externally, the cancelableXHR method returns an ABORT method that cancels the XHR request, in addition to a promise object that wraps the XHR.

delay-race-cancel.js
function cancelableXHR(URL) { req = XMLHttpRequest(); promise = Promise(function (resolve, reject) { req.open(, URL, ); req.onload = function () { (req.status === ) { resolve(req.responseText); } { reject( Error(req.statusText)); }}; req.onerror = function () { reject( Error(req.statusText)); }; req.onabort = function () { reject( Error(abort this request)); }; req.send(); }); Abort = function () {// Abort is called if the request has not yet ended https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest (req.readyState ! == XMLHttpRequest.UNSENT) { req.abort(); }}; return { promise: promise, abort: abort }; }Copy the code

Once these questions are understood, all that remains is the flow of Promise processing to code. The general process would look something like this.

  1. The cancelableXHR method gets the PROMISE object that wraps the XHR and the method to cancel the XHR request

  2. Make XHR’s wrapped promises compete with timeout promises in the timeoutPromise method via promise.race.

    • XHR returns the result before timeout

      1. As with normal promises, the result of the request is returned via then

    • When a timeout occurs

      1. Throw TimeoutError and catch it

      2. Call abort to cancel the XHR request if the catch error object is of type TimeoutError

Summarizing the above steps, the code looks like this.

delay-race-cancel-play.js
function copyOwnFrom(target, source) { Object.getOwnPropertyNames(source).forEach(function (propName) { Object.defineProperty(target, propName, Object.getOwnPropertyDescriptor(source, propName)); }); return target; } function TimeoutError() { superInstance = Error.apply(, arguments); copyOwnFrom(, superInstance); } TimeoutError.prototype = Object.create(Error.prototype); TimeoutError.prototype.constructor = TimeoutError; function delayPromise(ms) { return Promise(function (resolve) { setTimeout(resolve, ms); }); } function timeoutPromise(promise, ms) { timeout = delayPromise(ms).then(function () { return Promise.reject( TimeoutError(Operation timed out after + ms + )); }); return Promise.race([promise, timeout]); } function cancelableXHR(URL) { req = XMLHttpRequest(); promise = Promise(function (resolve, reject) { req.open(, URL, ); req.onload = function () { (req.status === ) { resolve(req.responseText); } { reject( Error(req.statusText)); }}; req.onerror = function () { reject( Error(req.statusText)); }; req.onabort = function () { reject( Error(abort this request)); }; req.send(); }); Abort = function () {// Abort is called if the request has not yet ended https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest (req.readyState ! == XMLHttpRequest.UNSENT) { req.abort(); }}; return { promise: promise, abort: abort }; } object = cancelableXHR(http://httpbin.org/get); // main timeoutPromise(object.promise, ).then(function (contents) { console.log(Contents, contents); }).catch(function (error) { (error instanceof TimeoutError) { object.abort(); return console.log(error); } console.log(XHR Error :, error); });Copy the code

The above code implements timeout handling with promise objects that become resolved within a certain amount of time.

In general development situations, since this logic is used frequently, it is a good idea to split up the code and keep it in separate files.

4.5.5. Promise and operation method

Earlier in cancelableXHR, the Promise object and its manipulation methods were returned in a single object, which seemed a little confusing.

It’s a good practice from a code organization perspective to return only one value (the promise object) from a function, but since you can’t access the REQ variable created in the cancelableXHR method from the outside, So we need to write a special function (abort in the above example) to handle these internal objects.

You could also consider extending returned Promise objects to support abort methods, but since promise objects are value-abstracted objects, adding methods to the operation without limitation would complicate the whole thing.

We all know that it’s not a good habit to have a function that does too much work, so we don’t want to have a function that does everything, so maybe it’s a good idea to split the function like this.

  • Return a Promise object containing XHR

  • Accept the Promise object as a parameter and cancel the XHR request in that object

By organizing these processes into a module, you can easily extend them later, the work a function does is more concise, and the code is easier to read and maintain.

There are many ways to create a module (AMD,CommonJS,ES6 Module etc..) CancelableXHR will be used as a node.js module.

cancelableXHR.js
use strict;
 requestMap = {};
function createXHRPromise(URL) {
     req =  XMLHttpRequest();
     promise =  Promise(function (resolve, reject) {
        req.open(, URL, );
        req.onreadystatechange = function () {
             (req.readyState === XMLHttpRequest.DONE) {
                delete requestMap[URL];
            }
        };
        req.onload = function () {
             (req.status === ) {
                resolve(req.responseText);
            }  {
                reject( Error(req.statusText));
            }
        };
        req.onerror = function () {
            reject( Error(req.statusText));
        };
        req.onabort = function () {
            reject( Error(abort this req));
        };
        req.send();
    });
    requestMap[URL] = {
        promise: promise,
        request: req
    };
    return promise;
}

function abortPromise(promise) {
     (typeof promise === undefined) {
        return;
    }
     request;
    Object.keys(requestMap).some(function (URL) {
         (requestMap[URL].promise === promise) {
            request = requestMap[URL].request;
            return ;
        }
    });
     (request !=  && request.readyState !== XMLHttpRequest.UNSENT) {
        request.abort();
    }
}
module.exports.createXHRPromise = createXHRPromise;
module.exports.abortPromise = abortPromise;Copy the code

Using the method is also very simple. We use the createXHRPromise method to get the XHR promise object. When we abort the XHR, we just pass the promise object to the abortPromise(Promise) method.

 cancelableXHR = require(./cancelableXHR);

 xhrPromise = cancelableXHR.createXHRPromise(http://httpbin.org/get);(1)Xhrpromise.catch (function (error) {// Call abort throws an error}); cancelableXHR.abortPromise(xhrPromise);(2)Copy the code

1 Create a Promise object that wraps XHR
2 Cancel the request action for the Promise object created in 1

4.5.6. Summary

Here’s what we learned.

  • DelayPromise that becomes resolved after a certain amount of time

  • Timeout implementation based on delayPromise and promise.race

  • Cancel the XHR Promise request

  • The separation of promise objects and actions is achieved through modularity

Promise is a very flexible way to control the processing flow, and to get the most out of it, you need to be careful not to write a function too big and long, but break it up into smaller and simpler processes, and learn more about the mechanisms mentioned earlier in JavaScript.

4.6. What is promise.prototype. Done?

If you’ve used other Promise implementation libraries, you’ve probably seen examples of done instead of then.

Each of these libraries provides the promise.prototype. done method, which works just like then, but does not return a Promise object.

Although ES6 Promises and Promises/A+ are not designed to provide promise.prototype. done, many implementation libraries provide implementations of this method.

In this section, we’ll learn what promise.prototype. done is and why many libraries provide support for this method.

4.6.1. Code examples using done

It should be easy to understand the behavior of the Done method if you look at examples of code that actually uses done.

promise-done-example.js
(typeof Promise.prototype.done === undefined) { Promise.prototype. = function (onFulfilled, onRejected) { .then(onFulfilled, onRejected).catch(function (error) { setTimeout(function () { throw error; }); }); }; } promise = Promise.resolve(); promise.done(function () { JSON.parse(this is not json); // => SyntaxError: JSON.parse }); // => Open the console window in the developer tools of your browserCopy the code

As mentioned earlier, the Promise design specification doesn’t say anything about promise.prototype. done, so you can either use an implementation provided by your library or implement it yourself.

We’ll talk about how to do this ourselves later, but we’ll start by comparing then with done.

Scenarios using THEN
promise = Promise.resolve(); promise.then(function () { JSON.parse(this is not json); }).catch(function (error) { console.error(error); // => "SyntaxError: JSON.parse" });Copy the code

As we can see from the above, there are the following differences between the two.

  • Done does not return a Promise object

    • That is, method chains cannot be formed using methods such as catch after done

  • Exceptions that occur in done are thrown directly out

    • In other words, there will be no Error Handling as promised

Since done does not return a promise object, it is understandable that it should only appear at the end of a method chain.

In addition, we’ve already seen that Promise has powerful error handling, whereas done skips error handling in functions and throws exceptions directly.

Why do many libraries provide this function that contradicts the Promise feature? Take a look at the following example of how promises handle failures and maybe we can get some idea of why.

4.6.2. Disappearing errors

Promise has a powerful error-handling mechanism, but it also has a downside: it complicates human errors (when the debugger doesn’t run smoothly).

Maybe you remember when we were in then or Catch? I saw something similar in “.

As follows, let’s look at a function that returns a Promise object.

json-promise.js
function JSONPromise(value) {
    return  Promise(function (resolve) {
        resolve(JSON.parse(value));
    });
}Copy the code

This function passes the received parameters to json.parse and returns a Promise object based on json.parse.

We can use the Promise function as follows, since json.parse will parse the failure and throw an exception that will be caught by a catch.

function JSONPromise(value) { return Promise(function (resolve) { resolve(JSON.parse(value)); }); } // Run the example string = invalid JSON-encoded string; JSONPromise(string).then(function (object) { console.log(object); }). Catch (function(error){// => json. parse throws an exception when console.error(error); });Copy the code

This is fine if the failed exception is caught normally, but if you forget to handle the exception when you code, it can be tricky to find the source of the exception once it occurs, which is one aspect of using promises.

I forgot the exception handling example using catch
String = Invalid JSON-encoded string; JSONPromise(string).then(function (object) { console.log(object); });(1)Copy the code

1 Although an exception was thrown, it was not handled

Parse (‘ Syntax Error ‘) ¶ Parse (‘ Syntax Error ‘) ¶ parse (‘ Syntax Error ‘) ¶

Typo error
 string = ;
JSONPromise(string).then(function (object) {
    conosle.log(object);(1)
});Copy the code

1 There is a conosle spelling error

In this example, we misspelled console as conosle, so the following error occurs.

ReferenceError: conosle is not defined

However, because of the try-catch mechanism of promises, this problem can be internally digested. It would be nice to catch each call without missing anything, but it would be difficult to troubleshoot an error if it occurred during implementation.

The problem of having your mistake digested internally is also called unhandled rejection (Rejected) and has nothing to do with it.

How easy it is to check for unhandled rejection depends on how the Promise is implemented. For example, yPromise [49] prompts the console when it detects an unhandled Rejection error.

Promise rejected but no error handlers were registered to it

In addition, Bluebird[50] will directly display the obvious human errors, such as ReferenceError, on the console.

“Possibly unhandled ReferenceError. conosle is not defined

The Native Promise implementation addresses the same issue by providing gC-based unhandled rejection Tracking.

This feature is a mechanism for displaying an error when a promise object has been reclaimed by the garbage collector if it’s unhandled rejection.

The native Promise of Firefox[51] or Chrome[52] is partially implemented.

4.6.3. Done implementation

As a methodology, how does done address the above mentioned errors being ignored in Promise? In fact, the method is very simple and straightforward, that is to do error handling.

Since you can implement the done method on promises, let’s look at extending the Promise.prototype.done Promise prototype.

promise-prototype-done.js
use strict; (typeof Promise.prototype.done === undefined) { Promise.prototype. = function (onFulfilled, onRejected) { .then(onFulfilled, onRejected).catch(function (error) { setTimeout(function () { throw error; }); }); }; }Copy the code

So how does it throw the exception out of the Promise? What we’re actually doing here is using the throw method in setTimeout to throw the exception directly outside.

The setTimeout callback throws an exception
{
    setTimeout(function callback() {
        throw  Error(error);(1)}); }catch(error){ console.error(error); }Copy the code

1 This exception will not be caught

For reasons why exceptions thrown in asynchronous callback will not be caught, see the following.

  • JavaScript and asynchronous error handling – Yahoo! JAPAN Tech Blog[53]

A closer look at the promise.prototype. done code shows that this function returns nothing. In other words, done is handled according to the principle that “The Promise chain will be interrupted here, and if an exception occurs, just throw it out of the Promise.”

If the implementation and runtime implementation are perfect, unhandled rejection should be done, but not necessarily. In addition, like promise.prototype. done in this section, done can be implemented on top of existing Promises and is not part of ES6 Promises’ design specification.

In this articlePromise.prototype.doneThe implementation method of referencepromisejs.org[54]

4.6.4. Summary

In this section, we learned the basics and implementation details of done provided by the Promise libraries such as Q, Bluebird[55], and PrFun, and how the done method differs from the THEN method.

We’ve also learned that done has two characteristics.

  • Errors in done are thrown as exceptions

  • Put an end to Promise chain

And then the or catch? As mentioned in “Promise”, errors that are digested internally by promises may not be a big deal in most cases as debugging tools or libraries improve.

In addition, since done does not return a value, method chains cannot be created after it, so we can use the done method for consistency in the style of the Promise method.

ES6 Promises itself doesn’t offer a lot of features. Therefore, I think we may need to do our own extensions or use third party libraries in many cases.

We struggled to unify asynchronous processing with promises, but going too far could have complicated the system, so keeping the style consistent was an important part of making promises abstract objects.

Promises: The Extension Problem (part 4) | getiblog [56], introduces some methods of how to write Promise expansion program.

  • Extend the promise.Prototype method

  • Create an abstraction layer using Wrapper/Delegate

In addition, you can refer to Chapter 28. Subclassing Built-in [57] for detailed instructions on how to use a Delegate.

4.7. Promise and Method Chain

In a Promise you can link methods like then and catch together. This is very much like the method chain in DOM or jQuery.

The usual method chain is to concatenate methods by returning this.

For information on how to create method chains, please refer to the method of creating method chains – Aftertaste (Japanese Blog) [58].

On the other hand, since promises return a new Promise object each time, they look almost identical to a regular method chain.

In this section, we’ll learn how to rewrite Promise internally without changing the external interface of existing code written using method chains.

Method chains in 4.7.1. fs

We will use fs[59] in Node.js as an example.

In addition, the example here focuses on the code being easy to understand, so it may not be very practical in practice.

fs-method-chain.js
use strict;
 fs = require();
function () {
    .lastValue = ;
}
// Static method for File.prototype.read
File. = function FileRead(filePath) {
     file =  File();
    return file.read(filePath);
};
File.prototype. = function (filePath) {
    .lastValue = fs.readFileSync(filePath, utf-8);
    return ;
};
File.prototype.transform = function (fn) {
    .lastValue = fn.call(, .lastValue);
    return ;
};
File.prototype.write = function (filePath) {
    .lastValue = fs.writeFileSync(filePath, .lastValue);
    return ;
};
module.exports = File;Copy the code

This module can implement a chain of methods like read → Transform → write below.

 File = require(./fs-method-chain);
 inputFilePath = input.txt,
    outputFilePath = output.txt;
File.read(inputFilePath)
    .transform(function (content) {
        return >> + content;
    })
    .write(outputFilePath);Copy the code

The transform accepts as a parameter a method that processes its input parameters. In this example, we prefix the data read with a >> string.

4.7.2. The FS method chain based on Promise

Let’s rewrite the internal implementation using Promise without changing the external interface of the method chain.

fs-promise-chain.js
use strict;
 fs = require();
function () {
    .promise = Promise.resolve();
}
// Static method for File.prototype.read
File. = function (filePath) {
     file =  File();
    return file.read(filePath);
};

File.prototype. = function (onFulfilled, onRejected) {
    .promise = .promise.then(onFulfilled, onRejected);
    return ;
};
File.prototype[catch] = function (onRejected) {
    .promise = .promise.catch(onRejected);
    return ;
};
File.prototype. = function (filePath) {
    return .then(function () {
        return fs.readFileSync(filePath, utf-8);
    });
};
File.prototype.transform = function (fn) {
    return .then(fn);
};
File.prototype.write = function (filePath) {
    return .then(function (data) {
        return fs.writeFileSync(filePath, data)
    });
};
module.exports = File;Copy the code

The new then and catch additions can be thought of as aliases to internally stored promise objects, while the rest of the parts remain the same from an external interface perspective and are used in the same way as before.

Therefore, when using this module, we only need to change the module name of require.

 File = require(./fs-promise-chain);
 inputFilePath = input.txt,
    outputFilePath = output.txt;
File.read(inputFilePath)
    .transform(function (content) {
        return >> + content;
    })
    .write(outputFilePath);Copy the code

The file.prototype. then method calls this.promise.then and assigns the returned promise object to the internal promise object of the this.promise variable.

What’s the catch? The following pseudocode makes it easier to understand what’s going on behind the scenes.

File = require(./fs-promise-chain); File.read(inputFilePath) .transform(function (content) { return >> + content; }) .write(outputFilePath); Then (function (){return fs.readfilesync (filePath, utF-8); // => promise.then(function (){return fs.readfilesync (filePath, utF-8); }).then(function transform(content) { return >> + content; }).then(function write(){ return fs.writeFileSync(filePath, data); });Copy the code

See promise = promise.then(…) Writing this way makes it look like the promise will be overwritten, and you might wonder if the promise’s chain has been truncated.

You can think of it as something like promise = addPromiseChain(promise, fn); With this feeling, we added new processing to the Promise object and returned it, so it wouldn’t be a problem if we didn’t implement sequential processing ourselves.

4.7.3. The difference between the two

Synchronous and asynchronous

The biggest difference between fS-method-chain-js and Promise is synchronous and asynchronous.

If a method chain like fs-method-chain-js is queued, it can achieve almost the same function as an asynchronous method chain, but the implementation will become very complicated, so we chose a simple synchronous method chain.

As in the column: Can promises only be processed asynchronously? As described in “Promise”, only asynchronous operations are performed, so the method chain that uses Promise is also asynchronous.

Fs-method-chain-js does not contain any error-handling logic, but since it is a synchronous operation, the entire code can be wrapped in a try-catch.

Then and catch aliases to internal Promise objects are provided in the Promise version, so we can use catch for error handling just like any other Promise object.

Error handling in fs-promise-chain
 File = require(./fs-promise-chain);
File.read(inputFilePath)
    .transform(function (content) {
        return >> + content;
    })
    .write(outputFilePath)
    .catch(function(error){
        console.error(error);
    });Copy the code

Error handling can be a big problem if you want to implement asynchronous processing yourself in fs-method-chain-js; It’s easier to use promises when doing asynchronous processing.

4.7.4. Asynchronous processing outside of Promise

If you’re familiar with Node.js, you might think of Stream[60] when you see a method chain.

Using Stream[61] can eliminate the need to save this.lastValue and improve performance when handling large files. Also, using Stream might be faster than using Promise.

Read → Transform →write using Stream
readableStream.pipe(transformStream).pipe(writableStream);Copy the code

Therefore, it is not necessary to say that promises are always the best choice when processing asynchronously. You should choose the appropriate implementation method based on your purpose and actual situation.

Node.js Stream is an Event-based technology

More information about streams in Node.js can be found on the following page.

Stating the Promise wrapper

Back to the FS-method-chain-js and Promise versions, the internal implementation of these two methods is very similar. Is it possible to use the synchronous version of the code as an asynchronous version?

Since JavaScript can add methods to objects dynamically, it should theoretically be possible to automatically generate Promise code from non-Promise versions. (Of course, statically defined implementations are easier to handle.)

Although ES6 Promises does not provide such a feature, bluebird[62], a well-known third-party Promise implementation library, provides a feature called Promisification.

If you use a library like this, you can dynamically add the promise version of methods to an object.

 fs = Promise.promisifyAll(require());

fs.readFileAsync(myfile.js, ).then(function(contents){
    console.log(contents);
}).catch(function(e){
    console.error(e.stack);
});Copy the code

The Promise of Array wrapper

What Promisification does in the past is not easy to understand by imagination. We can use the method of adding promises to native arrays as an example.

Native DOM or String also provides a lot of functionality for creating method chains in JavaScript. Array includes methods such as Map and filter, which return an Array type and can be used to easily form method chains.

array-promise-chain.js
use strict; function ArrayAsPromise(array) { .array = array; .promise = Promise.resolve(); } ArrayAsPromise.prototype. = function (onFulfilled, onRejected) { .promise = .promise.then(onFulfilled, onRejected); return ; }; ArrayAsPromise.prototype[catch] = function (onRejected) { .promise = .promise.catch(onRejected); return ; }; Object.getOwnPropertyNames(Array.prototype).forEach(function (methodName) { // Don't overwrite (typeof ArrayAsPromise[methodName] ! == undefined) { return; } arrayMethod = Array.prototype[methodName]; (typeof arrayMethod ! == function) { return; } ArrayAsPromise.prototype[methodName] = function () { that = ; args = arguments; .promise = .promise.then(function () { that.array = Array.prototype[methodName].apply(that.array, args); return that.array; }); return ; }; }); module.exports = ArrayAsPromise; module.exports.array = function newArrayAsPromise(array) { return ArrayAsPromise(array); };Copy the code

How are native Array and Arrayas Promises different when used? We can test the above code to see how they differ.

array-promise-chain-test.js
use strict; assert = require(power-assert); ArrayAsPromise = require(.. /src/promise-chain/array-promise-chain); describe(array-promise-chain, function () { function isEven(value) { return value % === ; } function double(value) { return value * ; } beforeEach(function () { .array = [, , , , ]; }); describe(Native array, function () { it(can method chain, function () { result = .array.filter(isEven).map(double); assert.deepEqual(result, [, ]); }); }); describe(ArrayAsPromise, function () { it(can promise chain, function (done) { array = ArrayAsPromise(.array); array.filter(isEven).map(double).then(function (value) { assert.deepEqual(value, [, ]); }).then(done, done); }); }); });Copy the code

We see that we can use the Array method in ArrayAsPromise as well. And, like the previous examples, the native Array is handled synchronously, whereas the ArrayAsPromise is handled asynchronously, which is where they differ.

If you look closely at the implementation of ArrayAsPromise, you may have noticed that all of array. prototype’s methods are implemented. However, array. prototype also has methods like array.indexof that do not return Array type data, and it would be a bit unnatural for those methods to support method chains as well.

The important point here is that we can create the Promise version of the API dynamically in this way for apis that have interfaces that receive the same type of data. If we are aware of the regularity of this API, we may discover some new ways to use it.

Function (error,result){} function(error,result){} function(error,result){} function(error,result){} function(error,result){} function(error,result){} Automatically create methods wrapped in Promise.

4.7.6. Summary

In this section we have mainly studied the following contents.

  • Promise version of the method chain implementation

  • Promise is not always the best choice for asynchronous programming

  • Promisification

  • Reuse of unified interfaces

ES6 Promises only offers some Core level features. Therefore, we may need to repackage our existing methods in a Promise way.

However, callbacks such as events, which have no limit on how many times they can be called, are not a good place to use Promises, and promises can’t always be the best choice.

When and when you should not use promises is not the purpose of this book. It is important to keep in mind that you should not use promises in everything. I think it is best to consider whether you should use promises or other methods based on your specific purpose and situation.

4.8. Use promises for sequence processing

In Chapter 2 promise.all, we learned how to get multiple Promise objects to start executing simultaneously.

But the promise. all method runs multiple Promise objects at the same time, and if you want to start B after A has finished, you can’t do anything about this sequential execution.

In addition, in the same chapter on Promises and arrays, we introduced a less efficient way to implement sequential processing using multiple THEN’s repeatedly.

In this section, we’ll show you how sequential processing works in promises.

4.8.1. Circular and sequential processing

The implementation in a method that uses multiple THEN iterations is as follows.

function getURL(URL) { return Promise(function (resolve, reject) { req = XMLHttpRequest(); req.open(, URL, ); req.onload = function () { (req.status === ) { resolve(req.responseText); } { reject( Error(req.statusText)); }}; req.onerror = function () { reject( Error(req.statusText)); }; req.send(); }); } request = { comment: function getComment() { return getURL(http://azu.github.io/promises-book/json/comment.json).then(JSON.parse); }, people: function getPeople() { return getURL(http://azu.github.io/promises-book/json/people.json).then(JSON.parse); }}; function () { function recordValue(results, value) { results.push(value); return results; PushValue = recordValue.bind(, []); pushValue = recordValue.bind(, []); return request.comment().then(pushValue).then(request.people).then(pushValue); } // Run the example main().then(function (value) {console.log(value); }).catch(function(error){ console.error(error); });Copy the code

As the number of elements in the request increases, we need to increase the number of calls to the then method

Therefore, if we put all the processing content in an array and work with the for loop, the increase in processing content will not be a problem. First we use the for loop to do the same thing as before.

promise-foreach-xhr.js
function getURL(URL) { return Promise(function (resolve, reject) { req = XMLHttpRequest(); req.open(, URL, ); req.onload = function () { (req.status === ) { resolve(req.responseText); } { reject( Error(req.statusText)); }}; req.onerror = function () { reject( Error(req.statusText)); }; req.send(); }); } request = { comment: function getComment() { return getURL(http://azu.github.io/promises-book/json/comment.json).then(JSON.parse); }, people: function getPeople() { return getURL(http://azu.github.io/promises-book/json/people.json).then(JSON.parse); }}; function () { function recordValue(results, value) { results.push(value); return results; PushValue = recordValue.bind(, []); pushValue = recordValue.bind(, []); Tasks = [request.ment, request.people]; promise = Promise.resolve(); // Start (I =; i < tasks.length; i++) { task = tasks[i]; promise = promise.then(task).then(pushValue); } return promise; } // Run the example main().then(function (value) {console.log(value); }).catch(function(error){ console.error(error); });Copy the code

So like promise = promise.then(task).then(pushValue); The code is to continuously process the promise and continuously cover the value of the promise variable to achieve the cumulative processing effect of the Promise object.

This approach, however, requires a temporary variable, promise, which is not as clean from a code-quality perspective.

If you use array.prototype. reduce instead of this loop, the code becomes much smarter.

4.8.2. Promise chain and reduce

If the above code were rewritten using array.prototype. reduce, it would look something like this.

promise-reduce-xhr.js
function getURL(URL) { return Promise(function (resolve, reject) { req = XMLHttpRequest(); req.open(, URL, ); req.onload = function () { (req.status === ) { resolve(req.responseText); } { reject( Error(req.statusText)); }}; req.onerror = function () { reject( Error(req.statusText)); }; req.send(); }); } request = { comment: function getComment() { return getURL(http://azu.github.io/promises-book/json/comment.json).then(JSON.parse); }, people: function getPeople() { return getURL(http://azu.github.io/promises-book/json/people.json).then(JSON.parse); }}; function () { function recordValue(results, value) { results.push(value); return results; } pushValue = recordValue.bind(, []); tasks = [request.comment, request.people]; return tasks.reduce(function (promise, task) { return promise.then(task).then(pushValue); }, Promise.resolve()); } // Run the example main().then(function (value) {console.log(value); }).catch(function(error){ console.error(error); });Copy the code

With the exception of the main function, everything else in this code is handled the same as when the for loop was used.

The second parameter to array.prototype. reduce sets the initial value for storing computed results. In this example, promise.resolve () is assigned to the Promise, and the task is request.ment.

The value returned from the first argument in reduce will be assigned as a promise for the next loop. That is, by returning a new promise object created by then, you implement a promise chain similar to the for loop.

The difference between using reduce and for loops is that reduce no longer requires the temporary variable PROMISE, so you don’t have to write promise = promise.then(task).then(pushValue); This is a big step forward.

While array.prototype.reduce is great for sequential processing in promises, the code above can be confusing to understand how it works.

So let’s write a function called sequenceTasks, which takes an array of tasks as an argument.

As you can easily imagine from the following call code, the function’s function is to execute the tasks sequentially.

 tasks = [request.comment, request.people];
sequenceTasks(tasks);Copy the code

4.8.3. Define functions for sequential processing

Basically we just need to reconstruct a function based on the reduce method.

promise-sequence.js
function sequenceTasks(tasks) {
    function recordValue(results, value) {
        results.push(value);
        return results;
    }
     pushValue = recordValue.bind(, []);
    return tasks.reduce(function (promise, task) {
        return promise.then(task).then(pushValue);
    }, Promise.resolve());
}Copy the code

One thing to note is that unlike, say, promise.all, this function takes an array of functions as an argument.

Why isn’t an array of Promise objects passed to this function? This is because by the time promise objects are created, XHR is already executing them, so sequential processing of these promise objects will not work properly.

So sequenceTasks takes as an array of functions that return a Promise object.

Finally, use sequenceTasks to rewrite the initial example words, as shown below.

promise-sequence-xhr.js
function sequenceTasks(tasks) { function recordValue(results, value) { results.push(value); return results; } pushValue = recordValue.bind(, []); return tasks.reduce(function (promise, task) { return promise.then(task).then(pushValue); }, Promise.resolve()); } function getURL(URL) { return Promise(function (resolve, reject) { req = XMLHttpRequest(); req.open(, URL, ); req.onload = function () { (req.status === ) { resolve(req.responseText); } { reject( Error(req.statusText)); }}; req.onerror = function () { reject( Error(req.statusText)); }; req.send(); }); } request = { comment: function getComment() { return getURL(http://azu.github.io/promises-book/json/comment.json).then(JSON.parse); }, people: function getPeople() { return getURL(http://azu.github.io/promises-book/json/people.json).then(JSON.parse); }}; function () { return sequenceTasks([request.comment, request.people]); } // Run the example main().then(function (value) {console.log(value); }).catch(function(error){ console.error(error); });Copy the code

What, is the flow in main() clearer?

As mentioned above, in a Promise, we can choose from several methods to implement sequential execution of the processing.

However, these methods are all based on JavaScript for loops or forEach that operate on arrays and things like that, and are essentially the same. So to some extent, it’s good practice to break up a chunk of processing into smaller functions when dealing with promises.

4.8.4. Summary

In this section, we describe how to implement the sequential processing of promises one by one in a Promise, as opposed to promise.all.

In order to realize sequential processing, we also introduced the implementation methods from procedural style coding to custom sequential processing functions, and again emphasized that we should follow the basic principle of dividing processing according to functions in the Promise domain.

You can also make a statement in the source code very long if you use the Promise chain to connect multiple processes in a Promise.

At this point, the overall structure of the code becomes very clear if we recall these basic principles of programming and split functions.

It’s also helpful to realize that Promise constructors and THEN are high-order functions, and that if you split the processing into functions, you can have the side effect of using those functions in flexible combinations.

A higher-order function is a function that can take an instance of a function object as its argument

5. Promises API Reference

5.1. Promise# then

promise.then(onFulfilled, onRejected);Copy the code

Then code sample
Promise = promise (function(resolve, reject){resolve(pass to then); }); promise.then(function (value) { console.log(value); }, function (error) { console.error(error); });Copy the code

This code creates a Promise object, defines the onFulfilled and onRejected function (handler), and then returns the Promise object.

The promise object calls the registered callback when it becomes resolve or Reject.

  • When the handler returns a normal value, this value is passed to the onFulfilled method of the Promise object.

  • This value is passed to the onRejected method of the Promise object when an exception is raised in the handler defined.

5.2. Promise# catch

promise.catch(onRejected);Copy the code

Catch code example
Promise = promise (function(resolve, reject){resolve(pass to then); }); promise.then(function (value) { console.log(value); }).catch(function (error) { console.error(error); });Copy the code

This is a syntactic sugar equivalent to promise.then(undefined, onRejected).

5.3. Promise. Resolve

Promise.resolve(promise);
Promise.resolve(thenable);
Promise.resolve(object);Copy the code

Promise.resolve code example
 taskName = task 1
asyncTask(taskName).then(function (value) {
    console.log(value);
}).catch(function (error) {
    console.error(error);
});
function asyncTask(name){
    return Promise.resolve(name).then(function(value){
        return Done! + value;
    });
}Copy the code

Different Promise objects are returned depending on the parameters received.

Although each case returns a Promise object, it generally falls into three main categories.

When the Promise object parameter is received

The received Promise object is returned

An object of type Thenable is received

Returns a new Promise object with a THEN method

When receiving arguments of other types (JavaScript pairs, null, etc.)

Return a new Promise object with that object as its value

5.4. Promise. Reject

Promise.reject(object)Copy the code

Promise.reject code example
 failureStub = sinon.stub(xhr, request).returns(Promise.reject( Error()));Copy the code

Return a new Promise object, reject, with the received value.

The value passed to promise. reject should also be an object of type Error.

Also, unlike promise.resolve, this function returns a completely new Promise object even if the argument promise.reject receives is a Promise object.

r = Promise.reject( Error(error)); console.log(r === Promise.reject(r)); // falseCopy the code

5.5. Promise. All

Promise.all(promiseArray);Copy the code

Promise.all code example
p1 = Promise.resolve(), p2 = Promise.resolve(), p3 = Promise.resolve(); Promise.all([p1, p2, p3]).then(function (results) { console.log(results); // [1, 2, 3]});Copy the code

Generate and return a new Promise object.

The method returns when all the promise objects in the promise array become resolve, and newly created promises use those promise values.

If any of the promises in the arguments is reject, the entire promise. all call terminates immediately and returns a new Reject Promise object.

Because each element in the parameters array is wrapped (wrap) by promise.resolve, paomise.all can handle different types of promose objects.

5.6. Promise. Race

Promise.race(promiseArray);Copy the code

Promise.race code example
 p1 = Promise.resolve(),
    p2 = Promise.resolve(),
    p3 = Promise.resolve();
Promise.race([p1, p2, p3]).then(function (value) {
    console.log(value);  
});Copy the code

Generate and return a new Promise object.

If any of the promise objects in the promise array are changed to resolve or reject, this function returns and uses the promise value to resolve or reject.

6. The language set

Promises

Promise specification itself

Promise object

Promise objects refer to promise instance objects

ES6 Promises

If you want to specify that you are using ECMAScript 6th Edition, use ES6 as a prefix.

Promises/A+

Promises/A + [63]. This is the predecessor of ES6 Promises, a community specification that has a lot in common with ES6 Promises.

Thenable

Class Promise object. Have an object named.then method.

promise chain

The act of connecting promise objects using then or catch methods. This is a word used in this book, not an official word defined in ES6 Promises.

w3ctag/promises-guide[64]

Promises Guide – There are a lot of concepts here

domenic/promises-unwrapping[65]

ES6 Promises Specification repo – Check the issue for details and information about the specification

ECMAScript Language Specification ECMA-262 6TH Edition — DRAFT

Promises – ES6 Promises – Promises are about to be promised

JavaScript Promises: There and back again – HTML5 Rocks[66]

The article about Promises – the sample code and reference here are very complete

Node.js Promise is here again! – Ac Ac Ac ac diary[67]

This article is referenced in the thenable section of the article on Node.js and Promise

8. About the author

azu[68] (Twitter : @azu_re[69] )

Focus on the latest technologies related to browsers and JavaScript.

He is adept at using ends as means, hence the book.

9. About translators

  • liubin github.com/liubin[70]

    • Except for kakau and Honnkyou, translation of the rest, proofreading of the whole, and translation of the source code and tools

  • kaku github.com/kaku87[71]

    • 1.1. What is Promise, 1.2. Introduction to Promise, 1.3. Write Promise code

  • honnkyou github.com/honnkyou[72]

    • 3.1. Basic tests

9.1. Leave a message and an afterword to the author

Postscript. PDF [73] describes why I wrote the book, how I wrote it, and how I tested it.

You can download the postscript for free at Gumroad or for any price you choose.

In the download, there will be a message to the author of the place, I hope you can write something to download later.

If you have any problems with this book, you can also submit it via GitHub or Gitter.