Write a promise

Take everyone to hand write a promis. Before I write it by hand, I’ll briefly explain why PROMISE is used, how promise is used, and how it’s used. Because we need to understand the requirements in order to better implement them.

This article adopts the usual nanny style, feeding to your mouth. Here, Taro, take your medicine

Introduction of promise

Promise solved the problem of using callback functions in asynchronous programming, nesting layers of hell callback, hell callback is not legible, logic is not clear, believe that anyone who took over a code like this would have to curse at what was written on it. Of course there are generator and async and await methods for asynchronous programming, and async and await have some advantages over Promise. I’ll talk about it briefly at the end, but I won’t expand too much.

The callback hell

Let’s take a look at this code and see if it makes you uncomfortable, but actually writing business code is more complicated than this, like sending an Ajax request 1, which has a lot of business processing code in it, and then sending request 2, which depends on the result of request 1

let i = 0
setTimeout(() = > {
  let i = 1
  console.log(i)
  setTimeout((i) = > {
    let j = i + 2
    console.log(j)
    setTimeout(j= > {
      let k = j + 3
      console.log(k)
    }, 1000, j);
  }, 1000, i);
}, 1000);
Copy the code

Promise solved the problem

The above code is implemented with promise, which seems to feel like more code, but obviously the code logic is clearer, and the more nested, the more obvious the promise advantage.

let i = 0
let p = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    i = i + 1
    console.log(i)
    resolve(1)},1000)
}).then(data= > {
  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
      i = i + 2
      console.log(i)
      resolve(i)
    }, 1000);
  })
}).then(data= > {
  setTimeout(() = > {
    i = i + 3
    console.log(i)
  }, 1000);
})
Copy the code

Basic features and usage of promises

Basic features :(keep these features in mind, these are the specific requirements for implementing handwritten promises. It is normal to not understand them now, but you will understand them later in the implementation process.)

  • Promise has three states: Pending, fulfilled, and rejected
  • Pending can be fulfilled or rejected. Once the status changes, the status cannot be changed again
  • Resolve indicates success, the status changes from Pending to depressing, and the parameter value is received
  • Reject indicates failure, the status changes from Pending to depressing, and reason is received
  • The Then method receives two parameters, onFulfilled and onRejected, and executes onFulfilled after pending => fulfilled, and executes onFulfilled after Pending => Fulfilled
// index.js
new Promise((resolve, reject) = > {
  setTimeout(() = > {
    // The asynchronous operation succeeds
    resolve('hello')
    // The asynchronous operation fails
    // reject(' reject ')
  }, 2000)
}).then(data= > {
  console.log(data)
}, reason= > {
  console.log(reason)
})
Copy the code

Promise to realize

The first step is to implement a basic promise

// index.js -----
let Promise = require('./promise')
let p = new Promise((resolve, reject) = > {
  resolve('Success!')
  // reject(' reject ~')
})
p.then(data= > {
  console.log(data)
})
Copy the code

From the above code we and promise feature, let’s clarify the requirements:

  1. The promise returns an executor and callback method as an argument to the promise
  2. Executor accepts resolve and reject as Executor arguments, resolve(‘ successful ‘) accepts value, and Reject (‘ failed ~’) accepts Reason.
  3. Combined with the stipulation of promise, promise has three states, including pending, depressing and Rejected
  4. Implement a THEN method, accept two parameters as onFulfilled and onFulfilled, and execute onFulfilled after pending => fulfilled. Execute onRejected after pending => Rejected

Code implementation

  1. The promise returns an executor and callback method as an argument to the promise

    // promise.js
    class Promise {
    	constructor(executor) {
        excutor()
      }
    }
    module.exports = Promise
    Copy the code
  2. Executor accepts resolve and reject as Executor arguments, resolve(‘ successful ‘) accepts value, and Reject (‘ failed ~’) accepts Reason.

    // promise.js
    class Promise {
    	constructor(executor) {
        this.value = undefined
        this.reason = undefined
        const resolve = (value) = > {
          this.value = value
        }
        const reject = (reason) = > {
          this.reason = reason
        }
        excutor(resolve, reject)
      }
    }
    module.exports = Promise
    Copy the code
  3. Combined with the stipulation of promise, promise has three states, including pending, depressing and Rejected

    // promise.js
    class Promise {
    	constructor(executor) {
        // Define a state, starting with pending
        this.state = 'pending'
        this.value = undefined
        this.reason = undefined
        const resolve = (value) = > {
          if (this.state === 'pending') {
          // Successful, the status changes from Pending to depressing
            this.state = 'fulfilled'
            // Received successfully ~
            this.value = value
          }
        }
        const reject = (reason) = > {
          if(this.state === 'pendinng') {
            // Failed. The status changes from Pending to Rejected
            this.state = 'rejected'
            // Failed to receive
            this.reason = reason
          }
    
        }
        excutor(resolve, reject)
      }
    }
    module.exports = Promise
    Copy the code
  4. Implement a THEN method, accept two parameters as onFulfilled and onFulfilled, and execute onFulfilled after pending => fulfilled. Execute onRejected after pending => Rejected

    class Promise {
      constructor(executor) {
        this.state = 'pending'
        this.value = undefined
        this.reason = undefined
        
        const resolve = (value) = > {
          if (this.state === 'pending') {
            this.value = value
            this.state = 'fulfilled'}}const reject = (reason) = > {
          if (this.state === 'pending') {
            this.reason = reason
            this.state = 'rejected'
          }
        }
        executor(resolve, reject)
      }
      // Implement the then method
      then(onFulfilled, onRejected) {
        / / success
        if (this.state === 'fulfilled') {
         // Receive data successfully
          onFulfilled(this.value)
        }
        / / fail
        if (this.state === 'rejected') {
          // Failed to receive data
          onRejected(this.reason)
        }
      }
    }
    module.exports = Promise
    Copy the code

    Run chestnut 1 and print successfully

The second step is to implement the asynchronous function publish and subscribe mode

Obviously chestnut 1 is a synchronous operation. If we look at Chestnut 2, we find that console.log in then does not print anything.

// index.js ------
let Promise = require('./promise')
let p = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('1s delay successful ~')},1000)
})
p.then(data= > {
  console.log(data)
})
Copy the code
  • Why is that?

Since all of our promises have been synchronous so far, when we execute our executor and execute the synchronization, we see an asynchronous setTimeout, queue her up (we need to know about event loops here), and immediately execute the then method.

The resolve method in setTimeout did not execute at all, the state is still pending, and the value did not get the delay of one second, so the state is still pending.

  • How do you solve it?

Since then has no way of knowing when and if RESOLVE has been executed, it needs something to tell then that resolve has been executed.

Clank ~ Publish Subscriber mode, Subscriber registers the event they want to Subscribe to the dispatch center, when Publisher publishes the event to the dispatch center, that is, when the event is triggered, The processing code registered with the dispatch center by the Fire Event subscriber.

For chestnuts, for example, we go to eat haidilao, haidilao front desk has a do hand membrane welfare, I don’t know how many hands front desk little sister and membrane to do, when it was my turn, I will go aboard his public appointments, waited in line for a number, to continue to eat my haidilao, then front desk little sister finished through the information in the public, I leave, Call me from platoon number so I can get a mask.

Let’s take a look at the requirements:

While pending, we keep track of which items will wait until resolve completes, and we put them in an array. Use resolve or reject before executing them.

class Promise {
  constructor(executor) {
    this.state = 'pending'
    this.value = undefined
    this.reason = undefined
    // Define an array to hold tasks to be completed later
    this.onResolvedCallbacks = []
    this.onRejectedCallbacks = []
    const resolve = (value) = > {
      if (this.state === 'pending') {
        this.value = value
        this.state = 'fulfilled'
        /* The state changes, and the tasks we have scheduled start */
        this.onResolvedCallbacks.forEach(fn= > fn())
      }
    }
    const reject = (reason) = > {
      if (this.state === 'pending') {
        this.reason = reason
        this.state = 'rejected'
        this.onRejectedCallbacks.forEach(fn= > fn())
      }
    }
    executor(resolve, reject)
  }
  then(onFulfilled, onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value)
    }
    if (this.state === 'rejected') {
      onRejected(this.reason)
    }
    if(this.state === 'pending') {
      /* State is still pending because of asynchrony */
      this.onResolvedCallbacks.push(() = > {
        onFulfilled(this.value)
      })
      this.onRejectedCallbacks.push(() = > {
        onRejected(this.reason)
      })
    }
  }
}
module.exports = Promise
Copy the code

Step 3: chain call

One of the advantages of promise is that it’s called in a chain, and it’s very logical, and we can always do this chain, chestnut 3

// index.js ------
let p = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('Delay 1 second succeeded ~')},1000)
})
p.then(data= > {
  let str = data + 'I'm in the first then oh! '
  console.log(str)
  return str
  /* return new Promise((resolve, reject) => {resolve(' resolver ')} */
})
}).then(data= > {
  let str = data + 'I'm in the second then! '
  console.log(str)
})
Copy the code

Requirements:

  1. The promise itself contains the then method, so to implement the chain call, we can just return a promise in the THEN. Place all tasks in the THEN method into a Promise promise((resolve, reject) => {tasks in the original THEN}) and then the next THEN.

    class Promise {
      constructor(executor) {
        this.state = 'pending'
        this.value = undefined
        this.reason = undefined
        this.onResolvedCallbacks = []
        this.onRejectedCallbacks = []
        const resolve = (value) = > {
          if (this.state === 'pending') {
            this.value = value
            this.state = 'fulfilled'
            this.onResolvedCallbacks.forEach(fn= > fn())
          }
        }
        const reject = (reason) = > {
          if (this.state === 'pending') {
            this.reason = reason
            this.state = 'rejected'
            this.onRejectedCallbacks.forEach(fn= > fn())
          }
        }
        executor(resolve, reject)
      }
      then(onFulfilled, onRejected) {
        // To get to the chained call then, we return a new promise instance
        const promise2 = new Promise((resolve, reject) = > {
          if (this.state === 'fulfilled') {
            This is a big pity. /* The result of ondepressing is the result of the callback function in March 3, and then the next method */ will be implemented immediately
            resolve(onFulfilled(this.value))
          }
          if (this.state === 'rejected') {
            reject(onRejected(this.reason))
          }
          if(this.state === 'pending') {
            this.onResolvedCallbacks.push(() = > {
              resolve(onFulfilled(this.value))
            })
            this.onRejectedCallbacks.push(() = > {
              reject(onRejected(this.reason))
            })
          }
        })
        return promise2
      }
    }
    module.exports = Promise
    Copy the code
  2. The Then method may return an ordinary value, or it may return a promise. If it’s a normal value, we pass it directly to value, but if it’s a promise, we also implement the promise’s resolve or reject.

    // index.js -- then can return a normal value, or it can return a promise
    let Promise = require('./promise')
    let p = new Promise((resolve, reject) = > {
      setTimeout(() = > {
        resolve('Delay 1 second succeeded ~')},1000)
    })
    p.then(data= > {
      let str = data + 'I'm in the first then.'
      console.log(str)
      return new Promise((resolve, reject) = > {
        resolve(str)
      })
    }).then(data= > {
      let str = data + 'I'm in the second then.'
      console.log(str)
    })
    Copy the code
    // promise.js
    class Promise {
      constructor(executor) {
        this.state = 'pending'
        this.value = undefined
        this.reason = undefined
        this.onResolvedCallbacks = []
        this.onRejectedCallbacks = []
        const resolve = (value) = > {
          if (this.state === 'pending') {
            this.value = value
            this.state = 'fulfilled'
            this.onResolvedCallbacks.forEach(fn= > fn())
          }
        }
        const reject = (reason) = > {
          if (this.state === 'pending') {
            this.reason = reason
            this.state = 'rejected'
            this.onRejectedCallbacks.forEach(fn= > fn())
          }
        }
        executor(resolve, reject)
      }
      then(onFulfilled, onRejected) {
        const promise2 = new Promise((resolve, reject) = > {
          if (this.state === 'fulfilled') {
            /* Since this is not the case, we use setTimeout to execute the contents in the next event loop where the */ has already been generated
            setTimeout(() = > {
              let x = onFulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            }, 0)}if (this.state === 'rejected') {
            setTimeout(() = > {
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            }, 0)}if(this.state === 'pending') {
            this.onResolvedCallbacks.push(() = > {
              setTimeout(() = > {
                let x = onFulfilled(this.value)
                resolvePromise(promise2, x, resolve, reject)
              }, 1000)})this.onRejectedCallbacks.push(() = > {
              setTimeout(() = > {
                let x = onRejected(this.reason)
                resolvePromise(promise2, x, resolve, reject)
              }, 0)})}})return promise2
      }
    }
    function resolvePromise(promise, x, resolve, reject) {
      if(typeof x === 'function'| | -typeof x === 'object'&& x ! = =null)) {
        try {
          const then = x.then
          if (typeof then === 'function') {
            // If an x is a promise, then two callbacks are executed, either successfully or unsuccessfully
            then.call(x, y= > {
              resolve(y)
            }, r= > {
              reject(r)
            })
          }
        } catch (err) {
          reject(err)
        }
      } else {
        // Just a normal value
        resolve(x)
      }
    }
    module.exports = Promise
    Copy the code

    So let’s do that

  3. What if we return promises with promises? Recursive!

    // index.js -----
    let Promise = require('./promise')
    let p = new Promise((resolve, reject) = > {
      setTimeout(() = > {
        resolve('Delay 1 second succeeded ~')},1000)
    })
    p.then(data= > {
      let str = data + 'I'm in the first then.'
      console.log(str)
      // The return value is promise package Promise
      return new Promise((resolve, reject) = > {
        resolve(str + new Promise((resolve, reject) = > {
          resolve(data + 'promise the promise')
          console.log(str + 'promise the promise')
        }))
      })
    }).then(data= > {
      let str = data + 'I'm in the second then.'
      console.log(str)
    })
    Copy the code
    // promise.js.function resolvePromise(promise, x, resolve, reject) {
      if(typeof x === 'function'| | -typeof x === 'object'&& x ! = =null)) {
        try {
          const then = x.then
          if (typeof then === 'function') {
            then.call(x, y= > {
              / / recursion
              resolvePromise(promise, y, resolve, reject)
            }, r= > {
              reject(r)
            })
          }
        } catch (err) {
          reject(err)
        }
      } else {
        resolve(x)
      }
    }
    ....
    Copy the code

I think it is almost the same, promise of course, there are all and other methods, next time I have time to more, today I am tired, do not want to write.

Reference Documents:

promise

Write a promise

The most detailed handwritten Promise tutorial ever