preface

Promise I believe we are familiar with, and a handwritten Promise is also a hot interview, so let us step by step handwritten a Promise

Build the skeleton of Promise

According to the PROMISE /A+ specification, the Promsie constructor returns an instance of A Promise object that has A THEN method. In the THEN method, there are two parameters of function type, which are onfulfilled and onRejected.

Wherein, onfulfilled can obtain the value of the Promise object after resolve through the parameter, and onRejected can obtain the value of the Promise object after reject.

Let’s first write the constructors and then methods according to the specification

function Promise(executor){}Promise.prototype.then=function(onfulfilled,onrejected){}Copy the code

This is a pity. When the Promise constructor is called with new, the executor parameter resolve will be called at the end of the asynchronous operation. The value of resolve can be obtained from ondepressing, the first function parameter of the subsequent THEN method. When an error occurs, the executor argument reject is called and the error message is executed as a reject function argument. The error message can be found in the second argument onRejected to the subsequent THEN method, as shown below

function Promise(executor){
   this.status = 'pending'
   this.value = null
   this.reason = null
    The arrow function ensures that resolve and reject get the Promise instance value
   const resolve=value= >{
       this.value =value
     }
   const  reject=reason= >{
       this.reason = reason
   }
   executor(resolve,reject)
   }
Promise.prototype.then = function(onfulfilled ,onrejected){
	onfulfilled(this.value)
    onrejected(this.error)
   }
Copy the code

Improve the state of Promise

Let’s test the previous code

let promise = new Promise((resolve,reject) = >{
    resolve('data')
    reject('error')
})
promise.then(data= >{
    console.log(data)
},error= >{
    console.log(error)
})
Copy the code

Executing the code above prints data and error, whereas a normal promise would print data

Because the instance state of a Promise can only change from Pending to depressing or from Pending to Rejected. And once the state has changed, it is impossible to change again. The following code

 function Promise(executor) {

     this.status = 'pending'
     this.value = null
     this.reason = null
     // Add judgment to the resovle and Reject methods, which only allow the Promsie instance state to change from pending to depressing or from pending to Rejected
     const resolve = value= > {
         if (this.status === 'pending') {
             this.value = value
             this.status = 'fulfilled'}}const reject = reason= > {
         if (this.status === 'pending') {
             this.reason = reason
             this.status = 'rejected'
         }

     }
     executor(resolve, reject)
 }
 Promise.prototype.then = function (onfulfilled , onrejected ) {

     if (this.status === 'fulfilled'){
         onfulfilled(this.value)
     }
    
     if (this.status === 'rejected'){
         onrejected(this.reason)
     }
   
 }
Copy the code

Ok, our code executes fine, but Promise is designed to solve asynchronous problems, and our code executes synchronously, so don’t worry, let’s fix the most important asynchronous logic

Write asynchronous logic for promises

Asynchronous preliminary implementation

Let’s test the following code first


 let promise = new Promise((resolve,reject) = >{
    setTimeout(() = >{
         resolve('data')},2000)
 })

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

I wait for 2s, and nothing comes out, right? ^? Normally, data will be output after two seconds

The reason is that the ondepressing in our then method is performed synchronously, and this. Status is still pending when it is performed, and the ondepressing is not performed 2s later.

The ondepressing method should be called at the appropriate time, which is when the developer calls Resolve. So we can save the ondepressing method passed in by the developer when the state is pending, and then execute it in the Resolve method. The following code

 function Promise(executor) {

     this.status = 'pending'
     this.value = null
     this.reason = null
     // Set the default value to function elements
     this.onfulfilledFunc = Function.prototype
     this.onrejectedFunc = Function.prototype
     const resolve = value= > {
         // Determine if value is an instance of Promise, if then is applied to it first
        if(value instanceof Promise) {return value.then(resolve,reject)
         }
         
             if (this.status === 'pending') {
                 this.value = value
                 this.status = 'fulfilled'
                 this.onfulfilledFunc(this.value)
             }   
         
    

     }
     const reject = reason= > {
         
             if (this.status === 'pending') {
                 this.reason = reason
                 this.status = 'rejected'
                 this.onrejectedFunc(this.reason)
             }
         
    

     }
     executor(resolve, reject)
 }
 Promise.prototype.then = function (onfulfilled , onrejected ) {
     if (this.status === 'fulfilled'){
          onfulfilled(this.value)
     }
    
     if (this.status === 'rejected'){
         onrejected(this.reason)
     }
     if (this.status === 'pending') {this.onfulfilledFunc = onfulfilled
         this.onrejectedFunc = onrejected
     }
   
 }

Copy the code

Put into a task queue

Let’s look at this test


 let promise = new Promise((resolve,reject) = >
 {
   setTimeout(() = >{
         resolve('data')},2000)
 })
 promise.then(data= >{
     console.log(data)
 })
 console.log(1)
Copy the code

Our code prints data, and then prints 1.

Normally, it prints 1 and then data.

The reason is that console.log(1) is a synchronous task, and asynchronous task queues are executed only when the stack of synchronous tasks is empty. Therefore, put resolve and Reject on the task queue.

We simply put a setTimeout into it to ensure asynchronous execution (in fact, promises are microtasks and can be simulated using MutationObserver)

 function Promise(executor) {

     this.status = 'pending'
     this.value = null
     this.reason = null
     this.onfulfilledFunc = Function.prototype
     this.onrejectedFunc = Function.prototype
     const resolve = value= > {
        if(value instanceof Promise) {return value.then(resolve,reject)
         }
         setTimeout(() = >{
               if (this.status === 'pending') {
                 this.value = value
                 this.status = 'fulfilled'
                 this.onfulfilledFunc(this.value)
             }   
         
         })
           

     }
     const reject = reason= > {
         setTimeout(() = >{
             if (this.status === 'pending') {
                 this.reason = reason
                 this.status = 'rejected'
                 this.onrejectedFunc(this.reason)
             }
         })
            
    

     }
     executor(resolve, reject)
 }
 Promise.prototype.then = function (onfulfilled , onrejected ) {
     if (this.status === 'fulfilled'){
          onfulfilled(this.value)
     }
    
     if (this.status === 'rejected'){
         onrejected(this.reason)
     }
     if (this.status === 'pending') {this.onfulfilledFunc = onfulfilled
         this.onrejectedFunc = onrejected
     }
   
 }
Copy the code

Save the callback function

Look at the following test

/ / test
 let promise = new Promise((resolve,reject) = >
 {
     setTimeout(() = >{
         resolve('data')},2000)
 })
 promise.then(data= >{
    console.log(` 1:${data}`)
 })
 promise.then(data= >{
     console.log(` 2:${data}`)})Copy the code

In theory it should output

1:data
2:data
Copy the code

But only 2:data is output because the onledFunc in the second THEN method overwrites the onledFunc in the first THEN method

So we just need to store the onimplemented func of onFulfilldedArray’s then method in an array, and just implement the methods of onFulfilldedArray’s array as the Promise is decided. The same goes for onRejected

 function Promise(executor) {

     this.status = 'pending'
     this.value = null
     this.reason = null
     this.onfulfilledArray = []
     this.onrejectedArray = []
     const resolve = value= > {
        if(value instanceof Promise) {return value.then(resolve,reject)
         }
         setTimeout(() = >{
               if (this.status === 'pending') {
                 this.value = value
                 this.status = 'fulfilled'
                   // The forEach method executes the methods in turn
                 this.onfulfilledArray.forEach(func= >{
                     func(value)
                 })
             }   
         
         })
           

     }
     const reject = reason= > {
         setTimeout(() = >{
             if (this.status === 'pending') {
                 this.reason = reason
                 this.status = 'rejected'
                 this.onrejectedArray.forEach(func= >{
                     func(reason)
                 })
             }
         })
            
    

     }
     // If an error occurs in the constructor, the Promsie instance state will automatically change to Rejected
     try{
          executor(resolve, reject)
     }catch(e){
         reject(e)
     }
    
 }
 Promise.prototype.then = function (onfulfilled , onrejected ) {
     if (this.status === 'fulfilled'){
          onfulfilled(this.value)
     }
    
     if (this.status === 'rejected'){
         onrejected(this.reason)
     }
     if (this.status === 'pending') {// Store the ondepressing in an array
         this.onfulfilledArray.push(onfulfilled)
         this.onrejectedFunc.push(onrejected)
     }
   
 }
Copy the code

Next we continue to implement the chain call effect of the THEN method

A chain call to promsie then

Preliminary implementation of chain call

Let’s first look at what happens if the first THEN method returns a non-Promise instance

  let promise = new Promise((resolve,reject) = >
  {
      setTimeout(() = >{
         resolve('data')},3000)
  })
  promise.then(data= >{
      console.log(`first ${data}`)
      return `second ${data} `
  })
 .then(data= >{
      console.log(data)
   })
Copy the code

Each THEN method should return a Promise instance

 Promise.prototype.then = function (onfulfilled , onrejected ) {
     //Promise penetrates the implementation by giving a default value to the function that returns its argument if a non-function value is passed to the then() function
    onfulfilled = typeof onfulfilled === 'function'? onfulfilled:data= >data
    onrejected = typeof onrejected === 'function' ? onrejected:error= >{throw error};
   // promise2 will be the return value of the THEN method
    let promise2

  if (this.status === 'fulfilled') {return promise2 = new Promise((resolve,reject) = >{
        setTimeout(() = >{
            try{
               // The resolve value of this new promise2 is the result of ondepressing
               let x = onfulfilled(this.value)
               resolve(x)
            }catch(e){
                reject(e)
           }
        })
    })
 }
    
if (this.status === 'rejected') {return promise2 = new Promise((resolve,reject) = >{
        setTimeout(() = >{
            try {
                // The resolve value of this new promise2 is the result of onRejected
               let x = onrejected(this.value)
                 resolve(x)
             } catch (e) {
                 reject(e)
            }
         })
    })
 }
     // Promise2 is resolved when the ononledarray or onRejectedArray array functions are executed successively after asynchronous processing ends
     // A function of the ononledarray or onRejectedArray array switches the state of promise2 and performs the resolution
    if (this.status === 'pending') {return promise2 =new Promise((resolve,reject) = >{
         this.onfulfilledArray.push(() = >{
            try{
                 let x = onfulfilled(this.value)
                 resolve(x)
            }catch(e){
                 reject(e)
             }
        })
         this.onrejectedArray.push(() = >{
             try{
                 let x = onrejected(this.reason)
                 resolve(x)
             }
             catch(e){
                 reject(e)
             }
         })
       })
     }
   
 }
Copy the code

Code execution successful

Perfect chain calls

Let’s look at this example

const promise = new Promise((resolve, reject) = > {
    setTimeout(() = > {
        resolve('data')},3000)
})
promise.then(data= > {
    console.log(data)
    return new Promise((resolve, reject) = > {
        setTimeout(() = > {
            resolve(`${data} next`)},1000)
    })
})
.then(data= > {
        console.log(data)
    })
Copy the code

According to the Promise /A+ specification, X can be either A common value or A promise instance, so we abstract the resolvePromise method for unified processing, and the code is as follows

Promise.prototype.then = function (onfulfilled , onrejected ) {
    onfulfilled = typeof onfulfilled === 'function'? onfulfilled:data= >data
    onrejected = typeof onrejected === 'function' ? onrejected:error= >{throw error};
    
    let promise2

     if (this.status === 'fulfilled') {return promise2 = new Promise((resolve,reject) = >{
             setTimeout(() = >{
                 try{
             
                     let x = onfulfilled(this.value)
                     resolvePromise(promise2,x,resolve,reject)
                 }catch(e){
                     reject(e)
                 }
             })
         })
     }
    
     if (this.status === 'rejected') {return promise2 = new Promise((resolve,reject) = >{
             setTimeout(() = >{
                 try {
                     let x = onrejected(this.value)
                     resolvePromise(promise2,x,resolve,reject)
                 } catch (e) {
                     reject(e)
                 }
             })
         })
     }
    if (this.status === 'pending') {return promise2 =new Promise((resolve,reject) = >{
        this.onfulfilledArray.push(() = >{
            try{
                let x = onfulfilled(this.value)
                resolvePromise(promise2,x,resolve,reject)
            }catch(e){
                reject(e)
            }
        })
        this.onrejectedArray.push(() = >{
            try{
                let x = onrejected(this.reason)
                resolvePromise(promise2,x,resolve,reject)
            }
            catch(e){
                reject(e)
            }
        })
      })
    }
   
}
Copy the code

Finally, implement the resolvePromise function

const resolvePromise = (promise2, x, resolve, reject) = > {
    // Execute reject when X and promise2 are equal, i.e., when ondepressing returns promise2
    if (x === promise2) {
        reject(new TypeError('error due to circular reference'))}// Whether you have performed onfulfilled or onRejected
    let consumed = false

    let thenable
    // If Promsie is returned
    if (x instanceof Promise) {
        if (x.status === 'pending') {
            x.then(function (data) {
                // recursive call
                resolvePromise(promise2, data, resolve, reject)}, reject
            )
        } else {
            x.then(resolve, reject)
        }
        return
    }

    let isComplexResult = targert= > (typeof targert === 'function' || typeof targert === 'object') && (targert ! = =null)
    // If the Promsie type is returned
    if (isComplexResult(x)) {
        try {
            thenable = x.then
            // Determine if the return value is of type PROMISE
            if (typeof thenable === 'function') {
                thenable.call(x, function (data) {
                    if (consumed) {
                        return
                    }
                    consumed = true
                    // recursive call
                    return resolvePromise(promise2, data, resolve, reject)
                }, function (error) {
                    if (consumed) {
                        return
                    }
                    consumed = true
                    return reject(error)
                })
            }
            else {
                resolve(x)
            }
        } catch (e) {
            if (consumed) {
                return
            }
            consumed = true
            return reject(e)
        }
    }
    // If the value is normal
    else {
        resolve(x)
    }

}
Copy the code

Now that we’ve nailed down the chain calls to THEN, which are at the heart of handwritten promises, we’ll implement catch methods and other static methods together in the next article