Because JavaScript is typically executed by a single thread, asynchronous operations are often needed when writing JavaScript code to improve program performance. Asynchronous execution is typically implemented in JavaScript in the form of callback functions. However, in recent years, thanks to the promotion of the community, Promise has become a standard for asynchronous JavaScript programming. When using Promise for asynchronous programming, the maintainability of code will be greatly improved, especially when using Promise to replace the nesting of multi-layer callback functions.

PromiseIntroduction to the

Here’s an example of the simplest code to construct a Promise:

/ / Promise
const promise = new Promise(function (resolve, reject) {
    // Asynchronous operation code
    if(Asynchronous operation successful) {resolve(this.response);
    } else {
        reject(this.status); }});// Callback when the promise state changes
promise.then(function (res) {
    console.log(res);  // The callback was successfully executed
}, function (error) {
    console.log(error);  // Failed to execute the callback
})
Copy the code

A Promise is an object provided by JavaScript that can be constructed with the new keyword. The Promise constructor takes a function as an argument, which takes two other functions, resolve and reject, that are provided by the JavaScript engine itself and don’t need to be created manually.

This Promise object has three states, which are “Pending”, “fulfilled” and “rejected”. There are two kinds of state changes, one is from pending to depressing and the other is from pending to Rejected. Once the state changes, the state will be solidified and cannot be changed again. The function of resolve is to change the state of the Promise object from pending to depressing, and the function of reject is to change the state of the Promise object from pending to Rejected.

Typically, we write some asynchronous code at the top of the function we pass to the Promise constructor, such as an AJAX request, and when the asynchronous code executes successfully, we call resolve, which is a function that takes arguments, This will be a big pity. The resolve function can fulfil two functions: one is to change the state of the Promise to be fulfilled, and the other is that it can pass the parameters passed into the first callback function of the THEN method of the Promise instance. Use it in subsequent operations. Reject is called when the asynchronous code fails. Reject also changes the state of the Promise to Rejected and passes the parameters to the second callback of the Promise instance’s THEN method.

When the Promise state changes, we can retrieve the result of the asynchronous operation through the then method of the instance of the Promise object (the Promise variable). The then method accepts two callback functions as parameters. The first callback function will be called automatically when the Promise state becomes fulfilled, and the second callback function will be called automatically when the Promise state becomes Rejected. Each of these callback functions takes one argument, and the res argument received by the first callback is actually the argument we passed to resolve when the asynchronous code inside Promise executed successfully, The error argument received by the second callback function is passed to Reject when asynchronous code execution within Promise fails.

That’s how the Promise object works, but let’s use an example to show how Promise can be applied in real code.

Let’s start with an example of code that sends an AJAX request using the callback function:

function handleResponse() {
    if (this.readyState === 4) {
        if (this.status === 200) {
            console.log(this.response);
        } else {
            console.log(this.status); }}}const request = new XMLHttpRequest();
request.open('GET'.'http://httpbin.org/get');
request.onreadystatechange = handleResponse;
request.send();
Copy the code

This kind of code is very common when writing Javascript, so how do you write this code the way Promise is written? Take a look at the following example:

const promise = new Promise(function (resolve, reject) {
    function handleResponse() {
        if (this.readyState === 4) {
            if (this.status === 200) {
                resolve(this.response);  // Called when the request succeeds
            } else {
                reject(this.status);  // Called when the request fails}}}const request = new XMLHttpRequest();
    request.open('GET'.'http://httpbin.org/get');
    request.onreadystatechange = handleResponse;
    request.send();
})

promise.then(function (res) {
    console.log(res);  // Successful callback
}, function (error) {
    console.log(error);  // Failed callback
})
Copy the code

That’s how you write AJAX through Promise. At first glance, does it seem like more code and more trouble? As a matter of fact, it’s true that we don’t need to write Promise just to send a simple AJAX request code, which just makes the code look more complicated.

PromiseWhat problems can be solved

So what are the scenarios for Promise, and what problems can it solve?

  1. PromisePerfect for solvingThe callback hellThe problem.
  2. PromiseFixed not being able to use it in callback functionsreturnThe problem.

Let’s look at how Promise solves these two problems. Suppose we need to invoke an interface that gets a list of blogs posted by the user and requires a login to invoke it. That is to say, we must call the interface in the order: first call the login authentication interface to log in, and then call the interface to get the list of users’ blogs.

If we were using jQuery to send an AJAX request as a callback, we might write code like this:

// Send the first AJAX request for login
$.ajax({
    type: 'GET'.url: 'http://localhost:3000/login'.success: function (res) {
        // Inside the successful login callback, send a second AJAX request to get the data
        $.ajax({
            type: 'GET'.url: 'http://localhost:3000/blog/list'.success: function (res) {
                console.log(res);
            },
            error: function (xhr) {
                console.log(xhr.status); }}); },error: function (xhr) {
        console.log(xhr.status); }});Copy the code

As you can see, the code written using the callback function form is nested. That is, with each additional request, we write another layer of nesting inside the Success callback function. If there are too many levels of nesting, then you have the JavaScript headache of callback hell. The more levels of nesting, the harder the code is to understand, and debugging can be a painful process if exceptions occur.

We tried to solve this problem with Promise:

const getResponse = function (url) {
    const promise = new Promise(function (resolve, reject) {
        $.ajax({
            type: 'GET'.url: url,
            success: function (res) {
                resolve(res);
            },
            error: function (xhr) { reject(xhr); }}); });return promise;
}

getResponse('http://localhost:3000/login').then(function (res) {
    getResponse('http://localhost:3000/blog/list').then(function (res) {
        console.log(res);
    }, function (xhr) {
        console.log(xhr.status); })},function (xhr) {
    console.log(xhr.status);
});
Copy the code

This is code that refactored with Promises, but if you look closely, you’ll see that the code above is still writing Promise code with callbacks in mind. On the first call the method getResponse function after the success of the request http://localhost:3000/login, code execution then method, in the interior of the then method, call the method getResponse function again, Request http://localhost:3000/blog/list address this time, after a successful execution of a method then, inside then method can successfully get the user blog list interface to return the result.

The above code also suffers from callback hell, because if there are too many requests, we also need to call getResponse in layers of nested functions. This certainly doesn’t get rid of callback hell.

Here’s an example of using the Promise object correctly to resolve callback hell:

const getResponse = function (url) {
    const promise = new Promise(function (resolve, reject) {
        $.ajax({
            type: 'GET'.url: url,
            success: function (res) {
                resolve(res);
            },
            error: function (xhr) { reject(xhr.status); }}); });return promise;
}

getResponse('http://localhost:3000/login').then(function (res) {
    return getResponse('http://localhost:3000/blog/list');
}, function (xhr) {
    console.log(xhr.status);
}).then(function (res) {
    console.log(res);
}, function (xhr) {
    console.log(xhr.status);
});
Copy the code

In the Promise interface, the THEN method returns a new Promise instance, so we can write it as a chain call, with the then method followed by another THEN method, which could theoretically go on indefinitely.

Pay attention to the internal first then method, call the method getResponse (‘ http://localhost:3000/blog/list ‘) added in front of the return. It is the presence of this return that makes it possible to write without layers of nesting and to flatten the code. The then methods following the Promise instance are executed in turn, and the return result from the previous THEN method is passed as an argument to the next THEN method. If you use the callback method, there is no way to return internally, so you have to increase nesting indefinitely to solve the problem.

This code shows the true power of the Promise object, flattening out the layers of nested code. Each time you add a call to the then method, the code will be much more intuitive and you won’t need to write multiple layers of nested code.

PromisethenMethods andcatchmethods

From the previous introduction, we learned that Promise’s then method takes two callback functions as arguments. In fact, its second argument is not mandatory, and if you don’t care about exceptions, you can just write the first callback function.

Promise also provides a catch method for receiving exceptions, written like this:

getResponse('http://localhost:3000/login').then(function (res) {
    return getResponse('http://localhost:3000/blog/list');
}).catch(function (xhr) {
    console.log(xhr.status);
});
Copy the code

It is more intuitive and clear to handle exceptions this way, not passing a second callback inside the then method, but inside the catch method alone. In fact, the.catch(callback) method is equivalent to.then(undefined, callback).

PromiseallMethods andracemethods

The all method of a Promise can combine multiple Promise instances into a single Promise instance.

What’s the use of that? Assuming we need to wait for multiple asynchronous operations to complete before we can execute the next step, this is a good time to use the all method. This situation is not easy to solve using the form of callback functions, but using promises is simple.

We simulate the three asynchronous operations that need to wait by requesting http://httpbin.org/get three times, writing the following code:

const p1 = getResponse('http://httpbin.org/get');
const p2 = getResponse('http://httpbin.org/get');
const p3 = getResponse('http://httpbin.org/get');

const p = Promise.all([p1, p2, p3]);
p.then(res= > {
    console.log(res);
}).catch(err= > {
    console.log(err);
});
Copy the code

Each item in the array is a Promise object. The Promise instance P is the merged object. The states of P1, P2, and P3 determine the final state of P.

Only when the states of P1, P2 and P3 become a big pity at the same time, the state of P will become a big pity. If one of the states of P1, P2 and P3 changes to Rejected, the state of P changes to Rejected. When P’s state becomes progressively, the return results of P1, P2 and P3 will form a list, which will be automatically passed to the callback function of P’s then method. And the order of the list is determined by the order of p1, P2, and P3 in the array passed to the all method.

When the status of P1, P2, and P3 changes to Rejected, the catch method behind all is called only when it does not have its own catch method. Otherwise, only its own catch method is called.

As for the usage of race method and all method, the difference is that: As long as one of the states of P1, P2 and P3 becomes a big pity, the state of P will become a big pity. This is also the meaning of the word race. As long as one of the objects p1, P2 and P3 changes the state first, the state of P will also change and solidify. The code inside the other two objects will still execute, but the results are useless.

Because the code to use race simply replaces promise.all ([P1, p2, p3]) with Promise.race([P1, p2, p3]), code examples are not given here.

The above is an introduction to the use of Promise. In fact, Promise provides more than these interfaces, but other interfaces are rarely used, so I won’t talk too much about them here, and you can learn more after you are familiar with its usage.