Tepi technical team: one-year front-end handwritten Promise

Write Promise from zero

  • In this era of rapid front-end development, if you don't work hard today, you will become garbage tomorrow. Although this is seen on the expression package, I'm sure everyone has seen it. To be honest, this is rough, but this is the case.
  • To get back to business, 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 we look at the zero handwriting 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);
})
  • It can be seen that it is not difficult to print out the "correct" Promise usage in the browser. We just need to know that new Promise passes function(resolve, reject) to Promise, and then do what needs to be done in the function. When necessary, we can modify the constructor Then (RES = > {}) to obtain the corresponding value

Now let's analyze how Promise should be implemented

  • First, we should define a Promise structure
  • This paper considers the way of es5
(function (window) {
    function MyPromise(executor) {                                          // 1 define MyPromise constructor
            
        function resolve(value) {                                           // Define resolve                               
        }
        
        function reject(reason) {                                           // Define reject  
        }

        MyPromise.prototype.then = function (onResolved,onRejected) {       // Define then
        }

        MyPromise.prototype.catch = function (error) {                      // Define catch
        }

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

    }
    
    window.MyPromise = MyPromise;                  // 2 Export
    
})(window)
  • Then you can see the following implementation
(function (window) {
    // Promise  
    function Mypromise(executor) {
        const self = this;                                  // You need to define this point
        self.status = 'pennding';                           // Initialization status
        self.data = undefined;                              // The promise object specifies a to store the result data
        self.callbacks = [];                                // Structure of each element {onResolved() {}, onRejected() {}

        function resolve(value) {                           // resolve
            if (self.status !== 'pennding') {               // 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 there is a callback function to be executed, immediately execute the callback function onResolved
                setTimeout(() => {                          // The current scheme is to hang the task in the queue and create asynchrony
                    self.callbacks.forEach(callbacksObj => {
                        callbacksObj.onResolved(value)
                    })
                },0)
            }
        }

        function reject(reason) {                            //reject
            if (self.status !== 'pennding') {                // 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 there is a callback function to be executed, immediately execute the callback function onRejected
                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 status is still pending 
        const self = this;
        self.callbacks.push({
            onResolved,
            onRejected
        })

    }

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

    }

    window.Mypromise = Mypromise;
})(window);
<body>
    <script src="./promise.js"></script>
    <script>

        const p = new Mypromise((resolve, reject) => {
            setTimeout(() => {           // Because splitting then has not been processed yet, p.then needs to be executed first
                resolve(1)
                console.log("I'll go 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>

  • It can be seen that the execution structure is asynchronous, and the first step is successful.

Let's explain the above code,

  • 1. The first is written through es5, so we need to export. We use closures

  • 2. Create Promise constructor and export

  • 3. When we use promise, we all know to pass in a function with resolve and reject, and perform the required operations in the function,

So the accepted parameter is called the executor

  • 4. Two functions are executed in the actuator

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

  • 6. Change the status and value according to resolve and reject

  • 7. We all know the promise constructor, then method and catch method

  • 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 src="./promise1.js"></script>
    <script>

        const p = new MyPromise((resolve, reject) => {
            setTimeout(() => {            // Due to the reason of splitting, then has not been processed accordingly, and the state cannot be changed at this time
                resolve(1)
                console.log("I'll go 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>

  • How did it turn out wrong
  • Let's take a look again. Because the callback directly executed is executed synchronously, the task needs to be put into the queue

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

  • OK, the above is the simple version of Promise, which does not include the implementation of then.

Next, we upgraded 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) {                                          // Define 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(callbacksObj => {                         // If the callback function is 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 exceptions
      executor(resolve, reject);
    } catch (error) {
      reject(error)
    }
  }
  /* 
  MyPromise then() of prototype object
  Specify success and failure callbacks
  Returns a new callback function
  // The returned MyPromise result is determined by the result of onResolved/onRejected
  */
  MyPromise.prototype.then = function (onResolved, onRejected) { // Define 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 MyPromise result is determined by the result of onResolved/onRejected
        // 1. Throw exception MyPromise, the result is failure, and the reason is the result
        // 2. The returned result is mypromise. Mypromise is the current result
        // 3. The result returned is not mypromise value

        // You need to capture to know whether there is an exception
        try{
          const result = callback(self.data)
          // Determine whether it is 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 reject(error), the first point above
        }
      }
    
      // Judge the current status
      if (self.status === FULFILLED){                           // The status is full
        setTimeout(()=>{                                        // Execute asynchronous callback immediately
          handle(onResolved);
        })                                                        
      } else if (self.status === REJECTED){                     // The status is rejected
        setTimeout(()=>{                                        // Execute asynchronous callback immediately
          handle(onRejected);
        })
      }else{                                                    // Pending saves success and failure to the callbacks for caching
        self.callbacks.push({
          onResolved(value){                                    //The callback function is called in the function, and the result of MyPromise is changed according to the result of the callback function
            handle(onResolved)                                  //Why is there no setTimeout here, because it has been written above that after changing the state, go back to the callbacks to loop the callback function to be executed
          },
          onRejected(reason){
            handle(onRejected)
          }
        })
      }
    })
  }

  MyPromise.prototype.catch = function (onRejected) {               //Define then
    return this.then(undefined,onRejected)
  }
  window.MyPromise = MyPromise; // Export MyPromise
})(window)
  • I believe you see this code and I have the same reaction. If I go, why is it different from before
  • In fact, when I started to implement the handwritten Promise, I think it's really not difficult. If it's difficult to say that the implementation of then is a little more difficult than the above.
  • OK, let me explain next
  • 1. Firstly, some constants are defined on the basis of the original ones, which is convenient to use

  • 2. You know promise What does then return

  • 3. Then judge the status

  • 4. OK, next, let's operate the corresponding state

  • 5. We can see that there is no timer added to the pending state. Why not asynchronous? Because it is stored in callbacks, and then the loop judgment will be carried out when it is executed to the corresponding position, and it is asynchronous during the loop operation. Improve the pending

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

  • 7. Set the default value again

  • 8. Let's improve catch

Next, let's test

  • There seems to be no problem
  • Try catch again


  • The novice's first writing is still not very good, but each step is really knocked out through how he learns. I hope to help friends who want to learn promise as well as me. It's hard to write it directly from the beginning to the end. In fact, it takes a lot of time, but it receives a lot of goods.

Tags: Javascript node.js Front-end JQuery React

Posted by zytex on Sun, 22 May 2022 08:01:32 +0300