Hand write a Promise source code

The step-by-step process of fulfilling a Promise. Start by implementing a simple promise

class MyPromise {
  Execute the executor function immediately
  constructor(executor) {
    executor(this.resolve, this.reject)
  }
  onFulfilledCallbacks = [] // Define a successful callback array
  onRejectedCallbacks = [] // Define an array of failed callbacks
  status = 'PENDING' // Define A state, according to Promises/A+, once A state changes, it cannot be changed
  value = null // The state becomes FULLFULED, which is regarded as the successful callback (value) of the. Then
  reason = null // When the status changes to REJECTED, consider the.then callback to the onRejected function (reason).
  // As the first argument to execute executor immediately, it is used to trigger a successful callback. Then
  resolve = (value) = > {
    if(this.status === 'PENDING') {
      // If the state is' waiting ', change the state to 'succeeded'
      this.status = 'FULFILLED'
      // Provide arguments to the successful callback to.then
      this.value = value
      / / if it is an asynchronous call resolve, enclosing onFulfilledCallbacks in. Then collect the incoming onFulfulled function method
      this.onFulfilledCallbacks.forEach(fn= > fn(value))
    }
  }
  reject = (reason) = > {
    if(this.status === 'PENDING') {
    // If the state is' waiting ', change the state to 'failed' first.
      this.status = 'REJECTED'
      // Arguments provided to.then failed callback (reason)
      this.reason = reason
      // If you call reject asynchronously, this.onRejectedCallbacks will collect the onRejected function in the. Then method
      this.onRejectedCallbacks.forEach(fn= > fn(reason))
    }
  }
  then = function(onFulfilled, onRejected) {
    if(this.status === 'PENDING') {
      New MyPromise(resolve, reject) calls resolve or reject asynchronously.
      this.onFulfilledCallbacks.push((value) = > { onFulfilled(value) })
      this.onRejectedCallbacks.push((reason) = > { onRejected(reason) })
    }
    if(this.status === 'FULFILLED') {
      New MyPromise(resolve, reject) calls resolve synchronously.
      onFulfilled(this.value)
    }
    if(this.status === 'REJECTED') {
      Reject is called synchronously in new MyPromise(resolve, reject).
      onRejected(this.reason)
    }
  }
}

Copy the code

This completes the simplest of Promose shells.

So what we’re going to do is implement chain calls to dot then. How to chain call? Then returns a new Promise instance, so let’s implement it from the original.

    then(onFulfilled, onRejected) {
      const promise2 = new MyPromise((resolve, reject) = > {
        if(this.status === 'FULFILLED') {
          onFulfilled(this.value)
        } else if(this.status === 'REJECTED') {
          onRejected(this.reason)
        } else if(this.status === 'PENDING') {
          this.onFulfilledCallbacks.push((value) = > { onFulfilled(value) })
          this.onRejectedCallbacks.push((reason) = > { onRejected(reason) })
        }
      })
      // Return the new Promise to subsequent.then calls
      return promise2
    }
Copy the code

If.then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then() then()) then() then() then() then() then()) then() then() then()) So that’s the Promise instance that was returned by the last dot then, so it’s pretty obvious what to do next. Modify the then function above.

  then(onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) = > {
      if(this.status === 'FULFILLED') {
        let value = onFulfilled(this.value)
        resolve(value)
      } else if(this.status === 'REJECTED') {
        let reason = onRejected(this.reason)
        reject(reason)
      } else if(this.status === 'PENDING') {
        this.onFulfilledCallbacks.push((value) = > { onFulfilled(value) })
        this.onRejectedCallbacks.push((reason) = > { onRejected(reason) })
      }
    })
    // Return the new Promise to subsequent.then calls
    return promise2
  }
Copy the code

Let’s test that out

new MyPromise(resolve= > {
  resolve(1)
}).then(value= > {
  console.log(value)
  return value + 1
}).then(value= > {
  console.log(value)}) output:1 2
Copy the code

Ok, Promise/A+ :.then() can pass no arguments, so how do we pass resolve or reject to the next.then? Look at the code below.

  then(onFulfilled, onRejected) {
    // This is a big pity. If onFulfilled is not a function, we will turn it into a function and return the value passed by the last promise instance
    const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value
    // If onRejected is not a function, we will turn it into a function, which will be similar to realondepressing
    const realOnRejected = typeof onRejected === 'function' ? onRejected : reason= > {
      throw reason
    }
    const promise2 = new MyPromise((resolve, reject) = > {
      if(this.status === 'FULFILLED') {
        let value = realOnFulfilled(this.value)
        resolve(value)
      } else if(this.status === 'REJECTED') {
        let reason = realOnRejected(this.reason)
        reject(reason)
      } else if(this.status === 'PENDING') {
        this.onFulfilledCallbacks.push((value) = > { realOnFulfilled(value) })
        this.onRejectedCallbacks.push((reason) = > { realOnRejected(reason) })
      }
    })
    // Return the new Promise to subsequent.then calls
    return promise2
  }
Copy the code

Now test this feature

new MyPromise(resolve= > {
  resolve(1)
}).then().then(value= > {
  console.log(value)
})
Copy the code

Ok, if now. Then resolve is a Promise instance. The current version obviously doesn’t work. So how do we fix it?

  then(onFulfilled, onRejected) {
    // This is a big pity. If onFulfilled is not a function, we will turn it into a function and return the value passed by the last promise instance
    const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value
    // If onRejected is not a function, we will turn it into a function, which will be similar to realondepressing
    const realOnRejected = typeof onRejected === 'function' ? onRejected : reason= > {
      throw reason
    }
    const promise2 = new MyPromise((resolve, reject) = > {
      if(this.status === 'FULFILLED') {
        // If the return value of x is a Promise instance
        let x = realOnFulfilled(this.value)
        // We define a resolvePromise function that does the same thing: call reslove, but with a different treatment for the return value
        resolvePromise(x, resolve, reject)
      } else if(this.status === 'REJECTED') {
        // This is a pity if the return value of x here is a Promise instance
        let x = realOnRejected(this.reason)
        resolvePromise(x, resolve, reject)
      } else if(this.status === 'PENDING') {
        this.onFulfilledCallbacks.push((value) = > { realOnFulfilled(value) })
        this.onRejectedCallbacks.push((reason) = > { realOnRejected(reason) })
      }
    })
    // Return the new Promise to subsequent.then calls
    return promise2
  }
  
  function resolvePromise(x, resolve, reject) {
    if (typeof x === 'object' || typeof x === 'function') {
      if(x === null) {
        return resolve(x)
      }
      let then
      try {
        then = x.then
      } catch(err) {
        reject(err)
      }
      if (then === 'function') {
        try {
          then.call(
            x,
            y= > {
              // Call resolvePromise recursively if a Promise instance is returned and resolve a Promise instance
              resolvePromise(y, resolve, reject)
            },
            r= > {
              reject(r)
            }
          )
        } catch(err) {
        
        }
      } else {
        // If it is an object value, just call resolve
        resolve(x)
      }
    } else {
      // If it is a normal value, just call resolve
      resolve(x)
    }
  }
Copy the code

Test the

  new MyPromise(resolve= > {
    resolve(1)
  }).then(value= > {
    return new MyPromise(resolve= > {
      resolve(value + 1)
    })
  }).then(value= > {
    console.log(value)
  })
Copy the code

Ok, resolved successfully. So let’s say we have code now

  let p1 = new MyPromise(resolve= > {
    resolve(1)
  })
  p1.then(value= > {
    return p1
  })
Copy the code

Uncaught (in promise) TypeError: Chaining cycle detected for promise #< promise >, at this time we should check whether the return value of ondepressing in then is equal to the current promise instance, modify the source code

  then(onFulfilled, onRejected) {
    // This is a big pity. If onFulfilled is not a function, we will turn it into a function and return the value passed by the last promise instance
    const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value
    // If onRejected is not a function, we will turn it into a function, which will be similar to realondepressing
    const realOnRejected = typeof onRejected === 'function' ? onRejected : reason= > {
      throw reason
    }
    const promise2 = new MyPromise((resolve, reject) = > {
      if(this.status === 'FULFILLED') {
        // Add a queueMicrotask that reads promise2 to the next microtask, so that no error is reported when promise2 is not read
        queueMicrotask(() = > {
          // If the return value of x is a Promise instance
          let x = realOnFulfilled(this.value)
          // We define a resolvePromise function that does the same thing: call reslove, but with a different treatment for the return value
          resolvePromise(promise2, x, resolve, reject)
        })
      } else if(this.status === 'REJECTED') {
        // Add a queueMicrotask that reads promise2 to the next microtask, so that no error is reported when promise2 is not read
        queueMicrotask(() = > {
          try {
            // This is a pity if the return value of x here is a Promise instance
            const x = realOnRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error)
          } 
        }) 
      } else if(this.status === 'PENDING') {
        this.onFulfilledCallbacks.push((value) = > { realOnFulfilled(value) })
        this.onRejectedCallbacks.push((reason) = > { realOnRejected(reason) })
      }
    })
    // Return the new Promise to subsequent.then calls
    return promise2
  }
  
  function resolvePromise(promise, x, resolve, reject) {
    // Same reject error
    if (promise === x) {
      return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}if (typeof x === 'object' || typeof x === 'function') {
      if(x === null) {
        return resolve(x)
      }
      let then
      try {
        then = x.then
      } catch(err) {
        reject(err)
      }
      if (then === 'function') {
        try {
          then.call(
            x,
            y= > {
              // Call resolvePromise recursively if a Promise instance is returned and resolve a Promise instance
              resolvePromise(promise, y, resolve, reject)
            },
            r= > {
              reject(r)
            }
          )
        } catch(err) { reject(err); }}else {
        // If it is an object value, just call resolve
        resolve(x)
      }
    } else {
      // If it is a normal value, just call resolve
      resolve(x)
    }
  }
Copy the code

Ok, no problem, and finally post the full code

  class MyPromise {
    constructor(executor) {
      try {
        executor(this.resolve, this.reject)    
      } catch(err) {
        throw err
      }
    }
    onFulfilledCallbacks = []
    onRejectedCallbacks = []
    status = 'PENDING'
    value = null
    reason = null 
    resolve = (value) = > {
      if(this.status === 'PENDING') {
        this.status = 'FULFILLED'
        this.value = value
        this.onFulfilledCallbacks.forEach(fn= > fn(value))
      }
    }
    reject = (reason) = > {
      if(this.status === 'PENDING') {
        this.status = 'REJECTED'
        this.reason = reason
        this.onRejectedCallbacks.forEach(fn= > fn(reason))
      }
    }
    then(onFulfilled, onRejected) {
      // This is a big pity. If onFulfilled is not a function, we will turn it into a function and return the value passed by the last promise instance
      const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value
      // If onRejected is not a function, we will turn it into a function, which will be similar to realondepressing
      const realOnRejected = typeof onRejected === 'function' ? onRejected : reason= > {
        throw reason
      }
      const promise2 = new MyPromise((resolve, reject) = > {
        const fulfilledMicrotask = () = >  {
          // Add a queueMicrotask that reads promise2 to the next microtask, so that no error is reported when promise2 is not read
          queueMicrotask(() = > {
            try{
              // If the return value of x is a Promise instance
              let x = realOnFulfilled(this.value)
              // We define a resolvePromise function that does the same thing: call reslove, but with a different treatment for the return value
              resolvePromise(promise2, x, resolve, reject)
            } catch(error) {
              reject(error)
            }
          })
        }
        const rejectedMicrotask = () = > {
          // Add a queueMicrotask that reads promise2 to the next microtask, so that no error is reported when promise2 is not read
          queueMicrotask(() = > {
            try {
              // This is a pity if the return value of x here is a Promise instance
              const x = realOnRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error)
            }
          }) 
        }
        if(this.status === 'FULFILLED') {
          fulfilledMicrotask()
        } else if(this.status === 'REJECTED') {
          rejectedMicrotask()
        } else if(this.status === 'PENDING') {
          this.onFulfilledCallbacks.push(fulfilledMicrotask)
          this.onRejectedCallbacks.push(rejectedMicrotask)
        }
      })
      // Return the new Promise to subsequent.then calls
      return promise2
    }
    catch (onRejected) {
      // Just error handling
      this.then(undefined, onRejected); }}function resolvePromise(promise, x, resolve, reject) {
    // Same reject error
    if (promise === x) {
      return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}if (typeof x === 'object' || typeof x === 'function') {
      if(x === null) {
        return resolve(x)
      }
      let then
      try {
        then = x.then
      } catch(err) {
        reject(err)
      }
      if (typeof then === 'function') {
        let called = false;
        try {
          then.call(
            x,
            y= > {
              if (called) return;
              called = true;
              // Call resolvePromise recursively if a Promise instance is returned and resolve a Promise instance
              resolvePromise(promise, y, resolve, reject)
            },
            r= > {
              if (called) return;
              called = true;
              reject(r)
            }
          )
        } catch(err) {
          if (called) return; reject(err); }}else {
        // If it is an object value, just call resolve
        resolve(x)
      }
    } else {
      // If it is a normal value, just call resolve
      resolve(x)
    }
  }
  MyPromise.deferred = function () {
    var result = {};
    result.promise = new MyPromise(function (resolve, reject) {
      result.resolve = resolve;
      result.reject = reject;
    });
    return result;
  }
  module.exports = MyPromise
Copy the code

To check the Promise/A+ specification, create A new package.json

  // package.json
  {
    "name": "promise"."version": "1.0.0"."description": "my promise"."main": "promise.js"."scripts": {
        "test": "promises-aplus-tests promise"
    },
    "author": "Wayag"."license": "ISC"."devDependencies": {
        "promises-aplus-tests": "^ 2.1.2"}}Copy the code

Run NPM run test.

Let’s talk about the automatic execution of generator and the simple implementation of async await. Let’s look at the manual execution of generator.

function* myGenerator() {
    console.log(yield Promise.resolve(1)) / / 1
    console.log(yield Promise.resolve(2)) / / 2
    console.log(yield Promise.resolve(3)) / / 3
}
const gen = myGenerator()
gen.next().value.then(val= > {
  console.log(val) / / 1
  gen.next(val).value.then(val= > {
    console.log(val) / / 2
    gen.next(val).value.then(val= > {
      console.log(val) / / 3
      gen.next(val)
    })
  })
})
Copy the code

The next step is to replace the manual with automatic, recursive and we can do that very quickly.

function run (generator) {
  let g = generator()
  function _next (val) {
    let res = g.next(val)
    if (res.done) return res.value
    Promise.resolve(res.value).then(_next ,reject)
  }
  _next()
}
run(myGenerator)
Copy the code

According to async await we should return a Promise instance at the end and we need to modify the source code

function run (generator) {
  return new Promise((resolve, reject) = > {
    let g = generator()
    function _next (val) {
      let res = g.next(val)
      if (res.done) return resolve(res.value)
      Promise.resolve(res.value).then(_next ,reject)
    }
    _next()
  })
}
Copy the code