What is Promise? What problem do we solve with Promise?

Promises are a solution to asynchronous programming: syntactically, promises are an object from which to retrieve messages for asynchronous operations; In its original sense, it is a promise that will give you results over time. Promise has three states:
Pending, fulfiled, Rejected; Once the state has changed, it will never change. Once a Promise instance is created, it executes immediately.

I’m sure you often write code like this:

// execute fn2 if parameter a is greater than 10 and parameter fn2 is a methodfunction fn1(a, fn2) {
    if (a > 10 && typeof fn2 == 'function') {
        fn2()
    }
}
fn1(11, function() {
    console.log('this is a callback')})Copy the code

In general, we don’t see a lot of callback nesting, usually one or two levels, but in some cases, when a lot of callback nesting, the code can be very cumbersome, which can cause a lot of trouble in our programming. This situation is commonly known as callback hell.


And that’s where our promise came in

Promises are designed to solve two problems:

  • Callback hell, code is difficult to maintain, often the output of the first function is the input of the second function

  • Promises can support multiple concurrent requests, retrieving data from concurrent requests
  • This promise solves the problem of asynchrony, not asynchrony by itself



Es6 promise

Promise is a constructor with familiar methods all, Reject, and resolve on its own, and equally familiar methods like THEN and catch on its prototype.

Let’s have a new one

letP = new Promise((resolve, reject) => {// Do something asynchronoussetTimeout(() => {
        console.log('Execution completed');
        resolve('I am success!! ');
    }, 2000);
});Copy the code

The Promise constructor takes one argument: the function, and this function takes two arguments:
  • Resolve: Callback function after asynchronous operation is successfully executed
  • Reject: Callback function when an asynchronous operation fails

Then chain operation usage

Therefore, on the surface, Promise can only simplify the writing method of layer upon layer callback. In essence, the essence of Promise is “state”, which can be invoked in time by maintaining state and transferring state. It is much simpler and more flexible than passing callback function. So the correct scenario for using promises looks like this:

p.then((data) => {
    console.log(data);
})
.then((data) => {
    console.log(data);
})
.then((data) => {
    console.log(data);
});Copy the code


Reject:

Set the state of the Promise to Rejected so that we can capture it in THEN and perform the “failed” callback. Look at the code below.

    letP = new Promise((resolve, reject) => {// Do something asynchronoussetTimeout(function(){ var num = Math.ceil(Math.random()*10); // Generate random numbers from 1 to 10if(num<=5){
                resolve(num);
            }
            else{
                reject('The numbers are too big'); }}, 2000); }); p.then((data) => { console.log('resolved',data);
        },(err) => {
            console.log('rejected',err); });Copy the code
Two arguments are passed in then, and the THEN method can accept two arguments, the first corresponding to the resolve callback and the second to the reject callback. So we were able to get the data separately from them. Run this code many times and you will get one of two random results:


or

The use of the catch

We know that a Promise object has a catch method as well as a then method, so what does that do? This, like the second argument to THEN, specifies the reject callback. Here’s how it works:

p.then((data) => {
    console.log('resolved',data);
}).catch((err) => {
    console.log('rejected',err);
});Copy the code

The effect is the same as in the second argument to then. However, it also serves another purpose: if an exception is thrown when the resolve callback (the first argument to then above) is executed, it does not trap the JS, but instead goes into the catch method. Take a look at the following code:

p.then((data) => {
    console.log('resolved',data); console.log(somedata); // somedata undefined}). Catch ((err) => {console.log()'rejected',err);
});Copy the code

In the resolve callback, we console.log(somedata); The variable someData is undefined. If we don’t use the Promise, the code will run here and we’ll just get an error on the console and stop running. But here, you get something like this:



That is, it is inside the catch method and passes the cause of the error to the reason argument. Even if the code has an error, it will not report an error, which has the same functionality as our try/catch statement

The callback is performed based on the slower-running all. All takes an array of arguments, and the values in it eventually return a Promise object

Promise’s All method provides the ability to execute asynchronous operations in parallel and not execute callbacks until all asynchronous operations have been executed. Look at the following example:
let Promise1 = new Promise(function(resolve, reject){})
let Promise2 = new Promise(function(resolve, reject){})
let Promise3 = new Promise(function(resolve, reject){})

let p = Promise.all([Promise1, Promise2, Promise3])

p.then(funciton(){// all three are successful},function(){// if there is a failure, then the failure})Copy the code

With ALL, you can perform multiple asynchronous operations in parallel and process all the returned data in a single callback. Isn’t that cool? There is a scene is very suitable for this, some game materials more applications, when opening the web page, pre-load the need to use a variety of resources such as pictures, Flash and various static files. After everything is loaded, we’ll initialize the page.

The callback is based on the fastest runner

For example, we can use race to set a timeout for an asynchronous request and perform the corresponding operation after the timeout, as follows:


// Request an image resourcefunction requestImg(){
        var p = new Promise((resolve, reject) => {
            var img = new Image();
            img.onload = function(){
                resolve(img);
            }
            img.src = 'Picture path';
        });
        returnp; } // Delay function, used to time the requestfunction timeout(){
        var p = new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('Image request timed out');
            }, 5000);
        });
        return p;
    }
    Promise.race([requestImg(), timeout()]).then((data) =>{
        console.log(data);
    }).catch((err) => {
        console.log(err);
    });
Copy the code

The requestImg function asynchronously requests an image, and I wrote the address as “path to the image”, so it will definitely fail. The timeout function is an asynchronous operation with a delay of 5 seconds. We put the two functions that return the Promise object into race, and they race. If the image request is successful within 5 seconds, then the normal process is followed. If the image is not returned within 5 seconds, timeout wins and a catch message is displayed indicating that the image request has timed out. The running results are as follows:




Okay, I’m sure you’ve got the idea, so let’s write our own promise

Implement a promise according to promiseA+

Step 1: Implement successful and failed callback methods

To implement the function in the above code, is the most basic promise function. First, you need to create a constructor promise, which creates a promisel class that passes in an executor. The executor passes in two arguments: resolve and Reject. As I said, if you succeed, you don’t fail, and if you fail, you don’t succeed. So, by default, the call returns a success state on success and a failure state on failure. The code is as follows:

Class Promise {constructor (executor){// Default state is wait state this.status ='panding'; this.value = undefined; this.reason = undefined; // Store the successful callback this.onresolvedCallbacks = []; // Store the failed callback this.onRejectedCallbacks = [];letResolve = (data) => {//this refers to the instanceif(this.status === 'pending'){
                this.value = data;
                this.status = "resolved"; this.onResolvedCallbacks.forEach(fn => fn()); }}let reject = (reason) => {
            if(this.status === 'pending'){
                this.reason = reason;
                this.status = 'rejected'; this.onRejectedCallbacks.forEach(fn => fn()); }} try{// Exception executor(resolve,reject) may occur; }catch (e){ reject(e); // Promise failed}}Copy the code

The Promise A+ specification states that the failed function is executed when there is an exception error.

constructor (executor){ ...... try{ executor(resolve,reject); }catch(e){ reject(e); }}Copy the code

Step 2: Then method chain call

The then method is the most basic promise method and returns two callbacks, one successful and one failed, as follows:

    then(onFulFilled, onRejected) {
    if (this.status === 'resolved'(this. Value); // This is a big song. }if (this.status === 'rejected') {// Callback to the failed state onRejected(this.reason); }}Copy the code

let p = new Promise(function(){
    resolve('I am success'); }) p.then((data) => {console.log(data); },(err) => {}); p.then((data) => {console.log(data); },(err) => {}); p.then((data) => {console.log(data); },(err) => {});Copy the code

The result returned is:

I am success I am success I am successCopy the code

To do this, the previous code would have to be rewritten, and we could store the result of each resolve call in an array and the result of each reject call in an array. This is the
Why are two arrays defined above, traversed in resolve() and reject(), respectively. Therefore, before calling resolve() or reject(), we store the result of multiple then’s into an array in pending state, and the above code changes to:

  then(onFulFilled, onRejected) {
    if (this.status === 'resolved') {
      onFulFilled(this.value);
    }
    if (this.status === 'rejected') { onRejected(this.reason); } // Currently neither complete nor failedif (this.status === 'pending'Deposit) {/ / callback to the success of this. OnResolvedCallbacks. Push (() = > {onFulFilled (enclosing the value). }); / / deposit failed callback enclosing onRejectedCallbacks. Push (() = > {onRejected (enclosing a tiny); }); }}Copy the code

The Promise A+ specification specifies that the THEN method can be invoked chained

In a PROMISE, the chained call returns a new promise. The result of the first THEN, success or failure, will be returned to the success state in the next THEN, but if an exception is thrown in the first THEN, it will be returned to the failure state in the next THEN

When the chain call succeeds

The success of the chain call returns a value. There are many situations, and the possible results are outlined based on the examples given. So write the value returned by the chained call to a separate method. The method is passed four parameters: p2,x,resolve,reject. P2 refers to the promise returned last time,x refers to the result returned by running the promise, and resolve and reject are p2 methods. The code is written as:

function resolvePromise(p2,x,resolve,reject){
    ....
}
Copy the code

  • The return result cannot be itself

var p = new Promise((resovle,reject) => {
    returnp; // the result returned cannot be itself,})Copy the code

You never succeed or fail when you return yourself, so throw an error when you return yourself

function resolvePromise(p2,x,resolve,reject){
    if(px===x){
        return reject(new TypeError('I quote myself.')); }... }Copy the code

  • The return result could be a promise

functionResolvePromise (promise2, x, resolve, reject) {/ / judge whether x promise / / specified in the specification: we allow others to write, this code can realize our promise and the promise of othersif(promise2 === x){// Do not wait for completionreturn reject(new TypeError('Circular reference')); }; // x is an object or function other than nullif(x ! =null && (typeof x ==='object' || typeof x === 'function')) {letcalled; // Prevent the call from failing after successthenObject.defineproperty is an exceptionlet then= x.then; / / take xthenMethod {then: {}}if(typeof then= = ='function'If) {/ /thenCall (x,y => {// If y is a promise, continue the recursive promiseif(called) return;
                    called = true; ResolvePromise (promise2, y, resolve, reject)}, r = > {/ / as long as failure would have failedif(called) return;
                    called = true;
                    reject(r);  
                });
            }else{/ /thenResolve (x); resolve(x); } }catch (e){if(called) return;
            called = true;
            reject(e)
        }
    }else{//x = 123 x is a normal value for the nextthenThe successful argument resolve(x)}}Copy the code

  • Resolve (x) if the result may be a normal value;
  • A Promise can only be called successfully or failed once

That is, when the call succeeds, the call cannot fail again. If both calls are made, the first call is executed. The code part is the same as above

In my opinion, this place is quite round and needs to be clarified step by step slowly.

According to the Promise A+ specification principle, promise encapsulates A set of built-in methods in its own framework.

  • Catch ()
  • Parse all methods all()
  • Race to race ()
  • Generate a successful Promise resolve()
  • Generate a failed Promise Reject ()

Finally, attach all the source code for you to read carefully.

functionResolvePromise (promise2, x, resolve, reject) {/ / judge whether x promise / / specified in the specification: we allow others to write, this code can realize our promise and the promise of othersif(promise2 === x){// Do not wait for completionreturn reject(new TypeError('Circular reference')); }; // x is an object or function other than nullif(x ! =null && (typeof x ==='object' || typeof x === 'function')) {letcalled; // Prevent the call from failing after successthenObject.defineproperty is an exceptionlet then= x.then; / / take xthenMethod {then: {}}if(typeof then= = ='function'If) {/ /thenCall (x,y => {// If y is a promise, continue the recursive promiseif(called) return;
                    called = true; ResolvePromise (promise2, y, resolve, reject)}, r = > {/ / as long as failure would have failedif(called) return;
                    called = true;
                    reject(r);  
                });
            }else{/ /thenResolve (x); resolve(x); } }catch (e){if(called) return;
            called = true;
            reject(e)
        }
    }else{//x = 123 x is a normal value for the nextthenResolve (x)}} class Promise {constructor (executor){// The default state is the wait state this.status ='panding'; this.value = undefined; this.reason = undefined; // Store the successful callback this.onresolvedCallbacks = []; // Store the failed callback this.onRejectedCallbacks = [];letResolve = (data) => {//this refers to the instanceif(this.status === 'pending'){
                this.value = data;
                this.status = "resolved"; this.onResolvedCallbacks.forEach(fn => fn()); }}let reject = (reason) => {
            if(this.status === 'pending'){
                this.reason = reason;
                this.status = 'rejected'; this.onRejectedCallbacks.forEach(fn => fn()); }} try{// Exception executor(resolve,reject) may occur; }catch (e){ reject(e); // Promise failed}}then(onfuifle,onRejected){// To prevent worthy of passing onfuifle = typeof onfuifle ==='function' ? onFuiFilled : y => y;
        onRejected = typeof onRejected === 'function'? onRejected :err => {throw err; }letpromise2; // Next timethenMethods the promise ofif(this.status === 'resolved'){
           promise2 = new Promise((resolve,reject) => {
               setTimeout(() => {try{// Successful logic Failed logicletx = onFuiFilled(this.value); // If x is a promise, the promise takes its outcome as the outcome of promise2's success. As a result of the success of promise2, resolvePromise resolves the relationship between X and promise2 by passing in four parameters, the first is the returned promise and the second is the returned result. The third and fourth methods are resolve() and reject(), respectively. resolvePromise(promise2,x,resolve,reject) }catch(e){ reject(e); }}}, 0)); }if(this.status === 'rejected'){
            promise2 = new Promise((resolve,reject) => {
                setTimeout(() => {
                    try{
                        letx = onRejected(this.reason); The first is the returned promise, the second is the returned result, and the third and fourth are the resolve() and reject() methods, respectively. resolvePromise(promise2,x,resolve,reject) }catch(e){ reject(e); }}}, 0)); } // Currently neither complete nor failedif(this.status === 'pending'(resolve) {promise2 = new Promise (and reject) = > {/ / function to the success of each deposit to the success callback function in the array. This onResolvedCallbacks. Push (() = > {setTimeout(() => {
                        try{
                            letx = onFuiFilled(this.value); resolvePromise(promise2,x,resolve,reject); }catch(e){ reject(e); }}}, 0)); / / one after another, the function of the failure to deposit to the callback function failure in the array. This onRejectedCallbacks. Push (() = > {setTimeout(() => {
                        try{
                            let x = onRejected(this.reason);
                            resolvePromise(promise2,x,resolve,reject)
                        }catch(e){
                            reject(e)
                        }
                    },0)
                })
           })
       }
       returnpromise2; / / callthenReturn a new promise} catch (onRejected) {// Catch (onRejected)thenMethod has no shorthand for successreturn this.then(null, onRejected);
    }
}
Promise.all = function(Promises) {// Promises is an array of promisesreturn new Promise(function (resolve, reject) {
        letarr = []; //arr is the result of the final return valueleti = 0; // Indicates the number of successesfunction processData(index, data) {
            arr[index] = data;
            if(++i === promises.length) { resolve(arr); }}for (let i = 0; i < promises.length; i++) {
            promises[i].then(function(data) {processData(I, data)}, reject)}})} // Just one promise succeeds. If the first one fails, it fails promise.race =function (promises) {
    return new Promise((resolve, reject) => {
        for(var i = 0; i < promises.length; I ++) {promise [I]. Then (resolve,reject)}})function(value){
    returnnew Promise((resolve,reject) => resolve(value); } // Generate a failed promise promise.reject =function(reason){
    return new Promise((resolve,reject) => reject(reason));
}
Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise( (resolve, reject) =>  {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd
}
module.exports = Promise;
Copy the code

As for the summary of this Promise A+ specification, there will certainly be A lot of shortcomings, welcome to put forward valuable comments or suggestions, but also hope to help you get some knowledge!