TPI technology team: one year’s front-end handwritten promise

Time:2020-10-2

Promise from zero

  • In this era of rapid development of the front-end, if we don’t work hard today, we will become garbage tomorrow. Although this is an expression bag, I believe we all have seen it. To be honest, this is rough, but it is the truth.
  • To get to the point, let’s take a look at today’s content.
  • Last time we saw the key point of JS is also the difficulty “asynchronous”. Strike while the iron is hot. Today, let’s take a look at zero handwritten promise

Let’s look at the code first
First, let’s review the usage of promise

new Promise((resolve,reject)=>{
    Resolve ('correct ')
}).then(value =>{
    console.log(value);
})
  • You can see that the “correct” is printed in the browserPromiseIt’s not difficult to use. We just need to know new Promise toPromiseafferentfunction(resolve,reject)Then do what you need to do in the function. When you need to, you can set the constructor.then(res=>{})The corresponding value can be obtained

Let’s analyze how promise should be implemented

  • First, we should define a promise structure
  • In this paper, we consider the way of Es5
(function (window) {
    Function mypromise (executor) {// 1 defines the mypromise constructor
            
        Function resolve (value) {// define resolve                               
        }
        
        Function reject (reason) {// define reject  
        }

        MyPromise.prototype.then  =Function (onresolved, onrejected) {// defines then
        }

        MyPromise.prototype.catch  =Function (error) {// define catch
        }

        //Define the execution of the actuator
        executor(resolve,reject);

    }
    
    window.MyPromise  =Mypromise; // 2 Export
    
})(window)
  • You can then look at the execution below
(function (window) {
    // Promise  
    function Mypromise(executor) {
        Const self = this; // you need to define this point
        self.status  ='pending'; // initialization status
        self.data  =Undefined; // promise object specifies one to store the result data
        self.callbacks  =[]; // the structure of each element {onresolved() {}, onrejected() {}}

        function resolve(value) {                           // resolve
            if ( self.status  ! = ='pending ') {// because promise status can only be modified once
                return;
            }
            self.status  ='resolve '; // change to resolve                  
            self.data  =Value; // save the value of value                        
            if ( self.callbacks.length  >0) {// if the callback function to be executed, execute the callback function onresolved immediately
                SetTimeout (() = > {// the current scheme is to hang tasks in the queue and create asynchrony
                    self.callbacks.forEach(callbacksObj => {
                        callbacksObj.onResolved(value)
                    })
                },0)
            }
        }

        function reject(reason) {                            //reject
            if ( self.status  ! = ='pending ') {// because promise status can only be modified once
                return;
            }
            self.status  ='reject '; // change to reject
            self.data  =Reason; // save the value of reason                        
            if ( self.callbacks.length  >0) {// if the callback function to be executed, execute the callback function onrejected immediately
                setTimeout(() => {
                    self.callbacks.forEach(callbacksObj => {
                        callbacksObj.onRejected(reason)
                    })
                },0)
            }
        }

        Try {// if the actuator throws an exception
            executor(resolve, reject);
        } catch (error) {
            reject(error)
        }
    }

    // Promise.then()
    Mypromise.prototype.then = function (onResolved, onRejected) {
        //Suppose the current state is still pending 
        const self = this;
        self.callbacks.push({
            onResolved,
            onRejected
        })

    }

    // Promise.catch()
    Mypromise.prototype.carch = function (error) {

    }

    window.Mypromise = Mypromise;
})(window);
<body>
    <script></script>
    <script>

        const p = new Mypromise((resolve, reject) => {
            SetTimeout (() = > {// because the split then is not processed, it needs to be executed first by p.then
                resolve(1)
                console.log ("me first")
            }, 100)
        })

        p.then(value => {
            console.log("onResolve()1", value)
        }, reason => {
            console.l("onReject()1", reason)
        })

        p.then(value => {
            console.log("onResolve()2", value)
        }, reason => {
            console.l("onReject()2", reason)
        })
    </script>
</body>

  • We can see that the first step is asynchronous.

Let’s explain the code above,

  • 1. First, it was written through Es5, so we need to export that we used closures

  • 2. Create promise constructor and export

TPI technology team: one year's front-end handwritten promise

  • 3. When we use project, we all know to pass in a function with resolve and reject, and execute the operation to be performed in the function,

So the accepted parameter is called an executor

  • 4. Two functions are executed in the actuator

  • 5. We all know that promise has three states, which are pending during initialization

  • 6. According toresolve,rejectChange state, change value

  • 7. We all know that promise constructors also have then methods and catch methods

TPI technology team: one year's front-end handwritten promise

  • 8. Don’t forget that promise can also throw error, so the executor needs to be modified

  • 9. Finally, call and execute to see the result.
<body>
    <script></script>
    <script>

        const p = new MyPromise((resolve, reject) => {
            SetTimeout (() = > {// because of splitting, then has not been processed, so the status cannot be changed at this time
                resolve(1)
                console.log ("me first")
            }, 100)
        })

        p.then(value => {
            console.log("onResolve()1", value)
        }, reason => {
            console.l("onReject()1", reason)
        })

        p.then(value => {
            console.log("onResolve()2", value)
        }, reason => {
            console.l("onReject()2", reason)
        })
    </script>
</body>

  • Why is the result wrong
  • Let’s look at it again. Because the callback executed directly is synchronous, the task needs to be put into the queue

  • At this point, the result is correct.
  • Finally, don’t forget that promise state can only be modified once

  • OK, the simple version of promise above does not include the implementation of then.

Next, we upgrade then and the whole based on the above

  • The code is as follows
(function (window) {
  const PENDDING = 'pendding';
  const FULFILLED = 'fulfilled';
  const REJECTED = 'rejected';

  Function mypromise (executor) {// defines the mypromises constructor
    const self = this;
    self.status = PENDDING;
    self.data = undefined;
    self.callbacks = [];

    function resolve(value) {
      if (self.status === PENDDING) {
        self.status  =Fulfilled; // change mypromise status
        self.data  =Value; // the value of mypromise changes accordingly            
        SetTimeout (() = > {// asynchronous execution 
          self.callbacks.forEach (callbacks obj = > {// if the callback function to be executed, execute the callback immediately
            callbacksObj.onResolved(value)                              
          })
        })
      }
    }

    function reject(reason) {
      if (self.status === PENDDING) {
        self.status = REJECTED;
        self.data = reason;
        setTimeout(() => {
          self.callbacks.forEach(callbacksObj => {
            callbacksObj.onRejected(reason);
          });
        })
      }
    }

    Try {// mypromise can throw an exception
      executor(resolve, reject);
    } catch (error) {
      reject(error)
    }
  }
  /* 
  Then() of the mypromise prototype object
  Specify success and failure callbacks
  Returns a new callback function
  //The returned myproject result is determined by the result of onresolved / onrejected
  */
  MyPromise.prototype.then  =Function (onresolved, onrejected) {// defines then      
    const self = this;
    //Specifies the default value of the callback function (must be a function)
    onResolved = typeof onResolved==='function' ?  onResolved : value => value;
    onRejected = typeof onRejected==='function' ?  onRejected : reason => {throw reason};

    Return new mypromise ((resolve, reject) = > {// returns a new mypromise object
      function handle(callback) {
          //The returned myproject result is determined by the result of onresolved / onrejected
        //1. Throw an exception mypromise, the result is failure, and reason is the result
        //2. Mypromise is returned and mypromise is the current result
        //3. The returned value is not mypromise

        //You need to capture to know if there are any exceptions
        try{
          const result = callback(self.data)
          //Judge if it's mypromise
          if ( result instanceof MyPromise){
            //Only then knows the result
            result.then(value=>resolve(value),reason=>reject(reason))
          }else{
            resolve(result)
          }
        }catch(error){
          Reject (error) // the returned result is the first point above reject (error)
        }
      }
    
      //Judge the current status
      if ( self.status  ===Fulled) {// status is full
        SetTimeout (() = > {// execute asynchronous callback immediately
          handle(onResolved);
        })                                                        
      } else if ( self.status  ===Rejected) {// status is rejected
        SetTimeout (() = > {// execute asynchronous callback immediately
          handle(onRejected);
        })
      }Else {// pending saves success and failure in callbacks and caches them
        self.callbacks.push({
          The onresolved (value) {// function calls the callback function and changes the result of mypromise according to the result of the callback function
            Handle (onresolved) // why there is no setTimeout here, because it has been written above that the callback function to be executed in the callback loop after changing the state is written
          },
          onRejected(reason){
            handle(onRejected)
          }
        })
      }
    })
  }

  MyPromise.prototype.catch  =Function (onrejected) {// defines then
    return this.then(undefined,onRejected)
  }
  window.MyPromise  =Mypromise; // export mypromise
})(window)
  • I believe you see this code is the same reaction as me, I go, how is it different from before
  • In fact, when I started to implement the handwritten promise, I found it really not difficult. If I wanted to say that the implementation of then was a little more difficult than the above.
  • OK, I’ll explain it later
  • 1. First of all, some constants are defined on the original basis for easy use

  • 2. You know Promise.then What is the return

  • 3. Then judge the state

  • 4. OK, so let’s work on the corresponding states

  • 5. You can see that there is no timer added to the pending state. Why is it not asynchronous? Because it is stored in callbacks and executed to the corresponding position on the top, the loop judgment will be made, and the loop operation is asynchronous, so as to improve the pending

  • 6. From the previous code, we can see that there are four locations where the code similarity is very high, so define a function and encapsulate it (note that it should be in the new promise)

TPI technology team: one year's front-end handwritten promise

  • 7. Set the default again

TPI technology team: one year's front-end handwritten promise

  • 8. Let’s perfect the catch

TPI technology team: one year's front-end handwritten promise

Next, we test it

TPI technology team: one year's front-end handwritten promise

  • It doesn’t seem to be a problem
  • Try againcatch


  • The novice’s first writing is still not very good, but each step is through their own learning, the actual knock out, I hope to help and I want to learn promise friends. It’s very difficult to write it directly from the beginning to the back. In fact, it took a lot of time, but the goods were received quite a lot.

Recommended Today

Learning from jedis

The official account of WeChat:Love to ask CTOProfessional programming Q & a communitywww.askcto.com Introduction to jedis Redis not only uses commands to operate, but also uses program client to operate. Now basically, the mainstream languages have client support, such as Java, C, C #, C + +, PHP, go, etcSome Java clients are listed in […]