primers

This article is written for those of you who have some experience with promises. If you haven’t used promises yet, this article may not be for you

The overall structure of this article is as follows, and a Promise will be realized step by step.

Promise class

For one thing, promise is definitely a class that defines both resolve and Reject methods.

function Promise(executor) {
 // Initialize state to wait state
 this.state = 'pending';
 // Success value
 this.value = undefined;
 // Cause of failure
 this.reason = undefined;
 // store fn1 callback
 this.fn1Callbacks = [];
 // store fn2 callback
 this.fn2Callbacks = [];
  / / success
 let resolve = (a)= >{};/ / fail
 let reject = (a)= >{};// Execute immediately
 executor(resolve, reject);
}
Copy the code

The code above implements the body of the Promise constructor, but there are two problems:

  1. The executor can make an error, right, because it’s a method passed in by the user, something like this. If the executor fails, we need to catch it with a try catch, the value that the Promise should throw reject:

    new Promise(function(resolve, reject) {
      console.log(a)  // A is not defined
    })
    Copy the code
  2. Resolve, reject, or empty functions that we need to fill in with logic.

Continue to improve:

function Promise(executor){
    // Initialize state to wait state
    this.state = 'pending';
    // Success value
    this.value = undefined;
    // Cause of failure
    this.reason = undefined;
    let resolve = value= > {
        // If state changes, the resolve call fails
        if (this.state === 'pending') {
            // State changes to a successful state after the resolve call
            this.state = 'fulfilled';
            // Save the successful value
            this.value = value; }};let reject = reason= > {
        // State changes,reject calls fail
        if (this.state === 'pending') {
            // reject State changes to a failed state
            this.state = 'rejected';
            // Cause of storage failure
            this.reason = reason; }};// If the executor executes incorrectly, execute reject
    try{
        executor(resolve, reject);
    } catch(err) { reject(err); }}Copy the code

Sum it up with a picture:

The code above is not particularly complicated, and the then method below is a bit complicated.

Implement then methods

A Promise object has a THEN method that registers callbacks after the Promise state is established. The THEN method is called when the state of a Promise changes, whether it succeeds or fails

The then method can be used as follows:

// The then method takes two methods as arguments, one fn1 and one fn2
p1.then(function fn1(data){
    // The argument to the fn1 method, which gets the value of the Promise object
}, function fn2(err){
    // Parameter of the fn1 method, used to obtain the failure cause
})
Copy the code

From the above example, it is obvious that we can come to the conclusion that

  1. The THEN method can be called on the P1 instance. So the implementation of the THEN method is on top of the Promise prototype.

  2. The then method returns a Promise, and a new Promise(detail) object.

  3. The then method can be called multiple times, and each time a new Promise object is returned. The Promise state can be either fullfilled or resolve, depending on the return value of fn1 for that call to THEN.

Therefore, the implementation of the THEN method is simple enough to call different callback functions based on the Promise state

Here is the idea for the THEN method:

Let’s implement the then method:

// The then method takes two arguments, fn1 and fn2, which are the callback after a Promise succeeds or fails
Promise.prototype.then = function(fn1, fn2) {
  var self = this
  var promise2

  // Check fn1 and fn2
  fn1 = typeof fn1 === 'function' ? fn1 : function(v) {}
  fn2 = typeof fn2 === 'function' ? fn2 : function(r) {}

  if (self.status === 'resolved') {
    return promise2 = new Promise(function(resolve, reject) {
        //todo})}if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {
       //todo})}if (self.status === 'pending') {
    return promise2 = new Promise(function(resolve, reject) {
       // todo}}})Copy the code

First, the input parameters Fn1 and Fn2 are judged. The specification states that fN1 and FN2 are optional parameters.

Which means you can pass or you can’t pass. The callback passed in is also not a function type, so what happens? The specification says ignore it. Therefore, you need to determine the type of the callback and execute it if it is a function.

Second, there are three possible states for a Promise. We divide them into three if blocks, each of which returns a New Promise.

So, the next logical step is:

  • If the Promise state is Resolved, fn1 needs to be executed.

  • If the promise state is Rejected, fn2 needs to be executed.

  • If the promise state is pending, we can’t decide whether to call fn1 or fn2, so we have to store all the methods in the fn1Callback, fn2Callback array. Wait until the state of the Promise is determined.

Fill the following code with the logic above:

Promise.prototype.then = function(fn1, fn2) {
    var self = this
    var promise2
    fn1 = typeof fn1 === 'function' ? fn1 : function(v) {}
    fn2 = typeof fn2 === 'function' ? fn2 : function(r) {}
    if (self.status === 'resolved') {
        return promise2 = new Promise(function(resolve, reject) {
            // Put fn1 and fn2 in a try catch. After all, fn1 and fn2 are passed by the user
            try {
                var x = fn1(self.data)
                // Fn1 returns a value that is injected into the promise returned by THEN via resolve
                resolve(x)
            } catch (e) {
                reject(e)                
            }
        })
    }
    if (self.status === 'rejected') {
        return promise2 = new Promise(function(resolve, reject) {
            try {
                var x = fn2(self.data)
                reject(x)
            } catch (e) {
                reject(e)
            }
        })
    }
    if (self.status === 'pending') {
        return promise2 = new Promise(function(resolve, reject) {
            this.fn1Callback.push(function(value){
                try {
                    var x = fn1(self.data);
                    resolve(x)
                } catch (e) {
                    reject(e)
                }
            })
            this.fn2Callback.push(function(value) {
                try {
                    var x = fn2(self.data);
                    reject(x)
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
}
Copy the code
  • Incoming fn1, fn2 are users, may be an error, alas, so be on the try catch it

  • Fn1, the return value of fn2, we’ll call it x, and we’ll name it x in the specification, so keep it the same. The x value will be used frequently in the following articles.

The then function essentially wraps the return value of fn1 as a promise and returns it. The problem is, fn1’s return value is written by the developer and can be weird. In the code above, we assume that x is an ordinary value. In fact, there are different cases of x that we have to deal with separately:

  • If x is a normal value, as in the code above, then returns a normal promise by using the resolve method directly

    return new Promise((resolve) = > {
        var x = fn1(self.data);    
        resolve(x)
    })
    Copy the code
  • If x is a promise, you wait for the promise state to change and get the fullfilled value. And then we change the code to add a judgment

    return new Promise((resolve) = > {
         var x = fn1(self.data);
         if (x instanceof Promise) {
            x.then((data) = > {resolve(data)}, (e) => {reject(e)})
         } else {
             resolve(x)
         }
    })
    Copy the code
  • By rule, we need to be compatible with a variety of different ways of writing. For example, if x is an object and the object has a then method, known as a thenable object, we have to do something like this:

    return new Promise((resolve) = > {
        var x = fn1(self.data);
        if (x instanceof Promise) {
            x.then((data) = > {resolve(data)}, (e) => {reject(e)})
        } else if (typeof x.then === 'function'){
            x.then(function(y){
                resolve(y)
            }, function(e){
                reject(e)
            })
        } else {
            resolve(x)
        }
    })  
    Copy the code

Above, we added some logic to handle the various cases where x returns a value. We need to move this logic into a resolvePromise method, which is responsible for packaging all the weird x’s into a normal promise.

resolvePromise

The resolvePromise method wraps x into a normal promise

function resolvePromise(promise2, x, resolve, reject) {
    // To prevent circular references
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise! '));
    }
    // If x is a promise
    if (x instanceof Promise) {
        x.then(function (data) {
            resolve(data)
        }, function (e) {
            reject(e)
        });
        return;
    }
    
    // If x is object or function
    if((x ! = =null) && ((typeof x === 'object') | | (typeof x === 'function'))) {
        // Retrieving x. Chen may cause an error
        try {
            // get x.teng first
            var then = x.then;
            var called
            if (typeof then === 'function') {
                // Then. Call (this, fn1, fn2)
                then.call(x, (y) => {
                    // What is called for?
                    // There are some promiscuous implementations, such as fn1, fn2, which is supposed to implement a
                    // There are some then implementations where fn1 and fn2 are executed
                    // Only one can be called for fn1 and fn2, setting a called flag bit
                    if (called) {
                        return;
                    }
                    called = true;
                    return resolvePromise(promise2, y, resolve, reject);
                }, (r) => {
                    if (called) {
                        return;
                    }
                    called = true;
                    return reject(r);
                });
            } else{ resolve(x); }}catch (e) {
            if (called) {
                return;
            }
            returnreject(e); }}else{ resolve(x); }}Copy the code

Note the code above:

  • Var then = x. Chen (); Why is it possible to get an error when fetching an attribute on an object? Promises have many implementations (Bluebird, Q, etc.). Promises/A+ is just A standard. Promises can only be universal if everyone implements them according to this standard. If someone else implements a Promise Object that uses Object.defineProperty() to maliciously throw values incorrectly, we can prevent bugs in our code.

  • If the object has then, and then is a function type, it is considered a Promise object, and then is called using x as this.

  • If x === promise2, a circular reference is created and waits for completion. A circular reference error is reported. What happens if X is the same as promise2?

    let p2 = p1.then(function(data){
     console.log(data)
     return p2;
    })
    Copy the code

    In the example above, p1.then() returns p2, as does fn1. What’s the problem with that? There is no way for a promise to change the state without manually calling the resolve method. P2 ‘s status cannot be changed, it cannot be changed

  • We need different Promise implementations to be able to interact with each other by treating the return value of Fn1 / fn2, x, as a possible Promise object, known as Thenable in the standard, and calling the THEN method on X in the safest way possible. If everyone follows the standard implementation, then different promises can interact with each other. While the standard to be on the safe side, even if x returned with a then properties but it doesn’t follow the Promise of the standard objects (such as the two parameters in this x, then it is called, synchronous or asynchronous invocation (PS, in principle, then the two parameters need to be asynchronous calls, talk about below), or the wrong call them again, Or then is not a function at all), can be handled as correctly as possible.

Functions inside then need to be executed asynchronously

In principle, the promise. Then (onResolved, onRejected) function should be called asynchronously.

In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.

So how do you turn synchronous code into asynchronous execution? This can be simulated using the setTimeout function:

setTimeout((a)= >{
    // This incoming code is executed asynchronously
},0);
Copy the code

Use this technique to make all parts of the code then execution asynchronous using setTimeout, for example:

setTimeout((a)= > {
    try {
        let x = fn1(value);
        resolvePromise(promise2, x, resolve, reject);
    } catch(e) { reject(e); }},0);
Copy the code

Promise body structure

// 1. Define status
// 2. Array of fn1, fn2
// 3. Define resolve reject
// 4. Executor execution
function Promise(executor) {
    let self = this;
    
    self.status = 'pending';
    self.fn1Callback = [];
    self.fn2Callback = [];
    
    // resolve to do STH
    Change the state of this instance
    // 2. Change this to data
    // 3. Iterate over methods mounted on this fn1Callback
    function resolve(value) {
        if (value instanceof Promise) {
            return value.then(resolve, reject);
        }
        setTimeout((a)= > { // Execute all callbacks asynchronously
            if (self.status === 'pending') {
                self.status = 'resolved';
                self.data = value;
                for (let i = 0; i < self.fn1Callback.length; i++) { self.fn1Callback[i](value); }}}); }function reject(reason) {
        setTimeout((a)= > { // Execute all callbacks asynchronously
            if (self.status === 'pending') {
                self.status = 'rejected';
                self.data = reason;
                for (let i = 0; i < self.fn2Callback.length; i++) { self.fn2Callback[i](reason); }}}); }try {
        executor(resolve, reject);
    } catch(reason) { reject(reason); }}// 1. Verify parameters
// 2. According to statue, perform fn1, fn2 or save the actions performed fn1, fn2 in array
// 3. Wrap the return values of fn1 and fn2 into a promise using resolvePromise
Promise.prototype.then = function (fn1, fn2) {
    let self = this;
    let promise2;
    fn1 = typeof fn1 === 'function' ? fn1 : function (v) {
        return v;
    };
    fn2 = typeof fn2 === 'function' ? fn2 : function (r) {
        throw r;
    };
    
    // The promise state is resolved
    if (self.status === 'resolved') {
        // then() returns a promise, a promise
        return promise2 = new Promise(((resolve, reject) = > {
            setTimeout((a)= > { // Execute onResolved asynchronously
                try {
                    // execute fn1() to get the result x
                    // fn1 is returned by the user
                    let x = fn1(self.data);
                    // If x is simple, resolve(x);
                    // resolve(x);
                    // You need to wrap it with the resolvePromise method
                    resolvePromise(promise2, x, resolve, reject);
                } catch(reason) { reject(reason); }}); })); }if (self.status === 'rejected') {
        return promise2 = new Promise(((resolve, reject) = > {
            setTimeout((a)= > { // Execute onRejected asynchronously
                try {
                    let x = fn2(self.data);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(reason) { reject(reason); }}); })); }if (self.status === 'pending') {
        // There is no asynchronous execution because these functions must be called resolve or reject, which are defined in the constructor for asynchronous execution
        return promise2 = new Promise(((resolve, reject) = > {
            // Define a method and mount it to the onResolvedCallback array
            // call fn1
            self.onResolvedCallback.push((value) = > {
                try {
                    let x = fn1(value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(r) { reject(r); }}); self.onRejectedCallback.push((reason) = > {
                try {
                    let x = fn2(reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(r) { reject(r); }}); })); }};// 1. Normal value
/ / 2. Promise value
// 3. Thenable, execute then
function resolvePromise(promise2, x, resolve, reject) {
    // To prevent circular references
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise! '));
    }
    // If x is a promise
    if (x instanceof Promise) {
        x.then(function (data) {
            resolve(data)
        }, function (e) {
            reject(e)
        });
        return;
    }
    
    // If x is object or function
    if((x ! = =null) && ((typeof x === 'object') | | (typeof x === 'function'))) {
        // Retrieving x. Chen may cause an error
        try {
            // get x.teng first
            var then = x.then;
            var called
            if (typeof then === 'function') {
                // Then. Call (this, fn1, fn2)
                then.call(x, (y) => {
                    // What is called for?
                    // There are some promiscuous implementations, such as fn1, fn2, which is supposed to implement a
                    // There are some then implementations where fn1 and fn2 are executed
                    // Only one can be called for fn1 and fn2, setting a called flag bit
                    if (called) {
                        return;
                    }
                    called = true;
                    return resolvePromise(promise2, y, resolve, reject);
                }, (r) => {
                    if (called) {
                        return;
                    }
                    called = true;
                    return reject(r);
                });
            } else{ resolve(x); }}catch (e) {
            if (called) {
                return;
            }
            returnreject(e); }}else{ resolve(x); }}Copy the code

Written Promise. All

This is a big pity. All need to wait until all the states of promises become fulfilled before resolve. But as long as one Promise fails, the result of failure will be returned.

Promise.all = function (arr) {
    return new Promise((resolve, reject) = > {
        if (!Array.isArray(arr)) {
            throw new Error(`argument must be a array`)}let dataArr = [];
        let num = 0;
        for (let i = 0; i < arr.length; i++) {
            let p = arr[i];
            p.then((data) = > {
                dataArr.push(data);
                num ++;
                if (num === arr.length) {
                    return resolve(data)
                }
            }).catch((e) = > {
                return reject(e)
            })
        }
    })
}
Copy the code

Written Promise. Retry

Retry is reject when an error is attempted more than a certain number of times

Promise.retry = function(getData, times, delay) {
    return new Promise((resolve, reject) = > {
        function attemp() {
            getData().then((data) = > {
                resolve(data)
            }).catch((err) = > {
                if (times === 0) {
                    reject(err)
                } else {
                    times--
                    setTimeout(attemp, delay)
                }
            })
        }
        attemp()
    })
}
Copy the code

Full score test

The open-source community provided A package to test Promises/ Aplus-tests to see if the promised source code was truly Promises/A+ compliant

This package can check whether the code we wrote is compliant one by one. If there is any inconsistency, it will report to us. If your code is green all the way through the check, congratulations, your Proimse has been legal and can be provided to others online.

This post appears on the Github blog: github.com/dujuncheng/… , there will be long-term updates in the future. Welcome to star

In addition

Bytes to beat (hangzhou) | Beijing | Shanghai a lot of hiring, welfare super salaries seconds kill BAT, not clock, every day to work in the afternoon tea, free snacks unlimited supply, free meals (I read the menu, Big gate crab, abalone, scallop, seafood, grilled fish fillet, beef, curry and spicy crayfish), free gym, touch bar, 15-inch top, new MBP, monthly rental allowance. This is really a lot of opportunities, the number of research and development after the expansion of N times, good technical atmosphere, cattle, less overtime, still hesitate what? Send your resume to the email below, now!

Just a small jd, more welcome to add wechat ~

The front end of jd: job.toutiao.com/s/bJM4Anjob…

The back-end jd: job.toutiao.com/s/bJjjTsjob…

Jd: test job.toutiao.com/s/bJFv9bjob…

Jd: job.toutiao.com/s/bJBgV8job…

The front-end intern: job.toutiao.com/s/bJ6NjAjob…

The back-end intern: job.toutiao.com/s/bJrjrkjob…

Continue to recruit a large number of front-end, server, client, testing, products, internship recruitment are wide to

Resume send [email protected], it is suggested to add wechat Dujuncheng1, you can chat to chat about life, please indicate from nuggets and where to deliver the post