I had learned to write promises by hand, but only “copied” the code based on what I understood. Therefore, the following article will summarize the teacher’s thoughts on handwriting and then record them.

For those of you who are not familiar with promise, please visit here.

Things we need to know before we write

  • The initial state of the promise is pending.
  • When resolve is called, the state becomes depressing.
  • When reject is called, the state changes to Rejected.
  • The state remains the same as long as it is changed from Pending.
  • When an error is thrown, it calls Reject. The status changes to Rejected.

According to the rules above, we can give the following structure

  • Define three state constants.
  • Defines the values passed successfully.
  • Define the cause of the failure.
    const PROMISE_STATUS_PENDING = 'pending'
    const PROMISE_STATUS_FULFILLED = 'fulfilled'
    const PROMISE_STATUS_REJECTED = 'rejected'

    class MyPromise {
      constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined

        const resolve = (value) = > {
          // Only when the state is pedding, it will assign value and change the state.
          if (this.status === PROMISE_STATUS_PENDING) {
            this.status = PROMISE_STATUS_FULFILLED
            this.value = value
            console.log("Resolve called")}}const reject = (reason) = > {
          if (this.status === PROMISE_STATUS_PENDING) {
            this.status = PROMISE_STATUS_REJECTED
            this.reason = reason
            console.log("Reject called")}}// Execute the executor immediately, the function that passes in promise. When the executor throws an error, it calls Reject directly to pass the error.
         try {
           executor(resolve, reject)
         } catch (error) {
           reject(error)
         }
      }
    }
Copy the code

test

    
const promise = new MyPromise((resolve, reject) = > {
  console.log("Pending")
  resolve(1111)
  reject(2222)})Copy the code

As you can see from the output above, when resolve is called, the promise state changes, and even if reject is called, it is never executed again.

Design of THEN method

  • The then method can take two arguments.
  • We keep this parameter and add it to the microtask when we call resolve or Reject, which satisfies the effect of the native Promise calling then and adding it to the microtask queue.

Version of a

    const PROMISE_STATUS_PENDING = 'pending'
    const PROMISE_STATUS_FULFILLED = 'fulfilled'
    const PROMISE_STATUS_REJECTED = 'rejected'

    class MyPromise {
      constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined

        const resolve = (value) = > {
          if (this.status === PROMISE_STATUS_PENDING) {
            this.status = PROMISE_STATUS_FULFILLED
            // Add it to the micro task queue
            queueMicrotask(() = > {
              this.value = value
              this.onFulfilled(this.value) }); }}const reject = (reason) = > {
          if (this.status === PROMISE_STATUS_PENDING) {
            this.status = PROMISE_STATUS_REJECTED
            queueMicrotask(() = > {
              this.reason = reason
              this.onRejected(this.reason)
            })
          }
        }

        try {
          executor(resolve, reject)
        } catch (error) {
          reject(error)
        }
      }

      then(onFulfilled, onRejected) {
        this.onFulfilled = onFulfilled
        this.onRejected = onRejected
      }
    }
Copy the code

Testing a

    const promise = new MyPromise((resolve, reject) = > {
      console.log("Pending")
      reject(2222)
      resolve(1111)})// Call the then method
    promise.then(res= > {
      console.log("res1:", res)
      return 1111
    }, err= > {
      console.log("err:", err)
    })

    promise.then(res= > {
      console.log("res2:", res)
    }, err= > {
      console.log("err2:", err)
    })
Copy the code

As can be seen from the above result, when the same promise is called many times, the then method will not be executed many times, because it will override the onFulfilled (onFulfilled) method last time, so it will be executed only once.

Test two

    // Call the then method asynchronously
    setTimeout(() = > {
      promise.then(res= > {
        console.log("res", res)
      })
    }, 1000);
Copy the code

According to the above results, setTimeout will be performed only after the promise is fulfilled, so ondepressing is not added yet, so ondepressing is undefined.

Version 2

For the above problems, we can make optimization

  • Define two arrays onenabled FNS and onRejectedFns to collect two parameters corresponding to the THEN method respectively.
  • Place the parameters passed by the then method in the corresponding array.
  • When we call the THEN method asynchronously, the state of the promise has already been determined, so we need to call the then method directly.
  • Non-asynchronous calls to THEN are also executed directly in the THEN method if the state-changing statement is inside the microtask, while calls to Reject and resolve are executed if the state-changing statement is outside the microtask. If (this.status! Resolve, reject, reject return == PROMISE_STATUS_PENDING) return (reject, reject, reject)
    const PROMISE_STATUS_PENDING = 'pending'
    const PROMISE_STATUS_FULFILLED = 'fulfilled'
    const PROMISE_STATUS_REJECTED = 'rejected'

    class MyPromise {
      constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledFns = []
        this.onRejectedFns = []

        const resolve = (value) = > {
          if (this.status === PROMISE_STATUS_PENDING) {
            // Add microtasks
            queueMicrotask(() = > {
              // If the state changes, the state will not change again, and the passed argument is called directly in the then method.
              if (this.status ! == PROMISE_STATUS_PENDING)return
              this.status = PROMISE_STATUS_FULFILLED
              this.value = value
              this.onFulfilledFns.forEach(fn= > {
                fn(this.value) }) }); }}const reject = (reason) = > {
          if (this.status === PROMISE_STATUS_PENDING) {
            // Add microtasks
            queueMicrotask(() = > {
              if (this.status ! == PROMISE_STATUS_PENDING)return
              this.status = PROMISE_STATUS_REJECTED
              this.reason = reason
              this.onRejectedFns.forEach(fn= > {
                fn(this.reason)
              })
            })
          }
        }

        executor(resolve, reject)
      }

      then(onFulfilled, onRejected) {
        If the state has already been determined at the time of the THEN call, in order to satisfy the asynchronous call to the THEN method
        if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
          onFulfilled(this.value)
        }
        if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
          onRejected(this.reason)
        }

        // Put successful and failed callbacks into an array
        if (this.status === PROMISE_STATUS_PENDING) {
          this.onFulfilledFns.push(onFulfilled)
          this.onRejectedFns.push(onRejected)
        }
      }
    }
Copy the code

Test three

    const promise = new MyPromise((resolve, reject) = > {
      console.log("Pending")
      resolve(1111) // resolved/fulfilled
      reject(2222)})// Call the then method multiple times
    promise.then(res= > {
      console.log("res1:", res)
    }, err= > {
      console.log("err:", err)
    })

    promise.then(res= > {
      console.log("res2:", res)
    }, err= > {
      console.log("err2:", err)
    })

    // After confirming the Promise state, then is called again
    setTimeout(() = > {
      promise.then(res= > {
        console.log("res3:", res)
      }, err= > {
        console.log("err3:", err)
      })
    }, 1000)
Copy the code

The problem with the above version is that it cannot be chain-called.

Version 3

  • The then method returns a promise directly.
  • It calls reject only when a method in THEN throws an exception. Otherwise resolve will be called.
    function execFunctionWithCatchError(execFn, value, resolve, reject) {
      try {
        const result = execFn(value)
        resolve(result)
      } catch(err) {
        reject(err)
      }
    }
Copy the code
    const PROMISE_STATUS_PENDING = 'pending'
    const PROMISE_STATUS_FULFILLED = 'fulfilled'
    const PROMISE_STATUS_REJECTED = 'rejected'

    // Utility functions
    function execFunctionWithCatchError(execFn, value, resolve, reject) {
      try {
        const result = execFn(value)
        resolve(result)
      } catch(err) {
        reject(err)
      }
    }

    class MyPromise {
      constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledFns = []
        this.onRejectedFns = []

        const resolve = (value) = > {
          if (this.status === PROMISE_STATUS_PENDING) {
            // Add microtasks
            queueMicrotask(() = > {
              if (this.status ! == PROMISE_STATUS_PENDING)return
              this.status = PROMISE_STATUS_FULFILLED
              this.value = value
              this.onFulfilledFns.forEach(fn= > {
                fn(this.value) }) }); }}const reject = (reason) = > {
          if (this.status === PROMISE_STATUS_PENDING) {
            // Add microtasks
            queueMicrotask(() = > {
              if (this.status ! == PROMISE_STATUS_PENDING)return
              this.status = PROMISE_STATUS_REJECTED
              this.reason = reason
              this.onRejectedFns.forEach(fn= > {
                fn(this.reason)
              })
            })
          }
        }

        try {
          executor(resolve, reject)
        } catch (err) {
          reject(err)
        }
      }

      then(onFulfilled, onRejected) {
        return new HYPromise((resolve, reject) = > {
          // If the state is already determined when then is called
          if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
            // try {
            // const value = onFulfilled(this.value)
            // resolve(value)
            // } catch(err) {
            // reject(err)
            // }
            execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
          }
          if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
            execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
          }

          // Put successful and failed callbacks into an array
          if (this.status === PROMISE_STATUS_PENDING) {
            // Wrap the function again to get the value returned
            this.onFulfilledFns.push(() = > {
              execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
            })
            this.onRejectedFns.push(() = > {
              execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
            })
          }
        })
      }
    }
Copy the code

The test of four

    const promise = new MyPromise((resolve, reject) = > {
      console.log("Pending")
      resolve(1111)})// Call the then method multiple times
    promise.then(res= > {
      console.log("res1:", res)
      return "aaaa"
    }, err= > {
      console.log("err1:", err)
      return "bbbbb"
    }).then(res= > {
      console.log("res2:", res)
    }, err= > {
      console.log("err2:", err)
    })
Copy the code

The above THEN method does not determine the value returned by the THEN method, but simply returns the plain value.

Design of catch method

  • When you throw an exception, call the onRejected of the next THEN method.
  • Simply define a default onRejected method and throw an error.
  • You simply call the second argument to the THEN method
  then(onFulfilled, onRejected) {
    // Then method is added
    + const defaultOnRejected = err= > { throw err }
    + onRejected = onRejected || defaultOnRejected
  }
  catch(onRejected) {
    // make it chainable
    return this.then(undefined, onRejected)
  }
Copy the code

test

   const promise = new MyPromise((resolve, reject) = > {
      console.log("Pending")
      reject(2222)})// Call the then method multiple times
    promise.then(res= > {
      console.log("res:", res)
    }).catch(err= > {
      console.log("err:", err)
    })
Copy the code

Finally method design

  • The implementation principle is similar to catch.
  • That is, a chain call using the then method, in which the finally argument is executed as an argument in the next then method.
  then(onFulfilled, onRejected) {
    // Then method is added
    + const defaultOnFulfilled = value= > { return value }
    + onFulfilled = onFulfilled || defaultOnFulfilled
  }
  finally(onFinally) {
    this.then(() = > {
      onFinally()
    }, () = > {
      onFinally()
    })
  }
Copy the code

test

    const promise = new MyPromise((resolve, reject) = > {
      console.log("Pending")
      resolve(1111)})// Call the then method multiple times
    promise.then(res= > {
      console.log("res1:", res)
      return "aaaaa"
    }).then(res= > {
      console.log("res2:", res)
    }).catch(err= > {
      console.log("err:", err)
    }).finally(() = > {
      console.log("finally")})Copy the code

Resolve, reject method design

  • Just call Promise. The corresponding resolve, reject methods are then called.
  static resolve(value) {
    return new MyPromise((resolve) = > resolve(value))
  }

  static reject(reason) {
    return new MyPromise((resolve, reject) = > reject(reason))
  }
Copy the code

test

    MyPromise.resolve("success=========").then(res= > {
      console.log("res:", res)
    })

    MyPromise.reject("error========").catch(err= > {
      console.log("err:", err)
    })
Copy the code

Design of all method

  • With the above methods implemented, the implementation of the All method is easy. Figure out how to use the all method.
  • This is a big pity. Only when all promise states in the promises array are fulfilled, will the promise’s resolve be called and the promise result will be returned in the array.
  • Call the Promise reject when a promise state in the promises array changes to Rejected.
  static all(promises) {
    return new MyPromise((resolve, reject) = > {
      const values = []
      promises.forEach(promise= > {
        promise.then(res= > {
          values.push(res)
          if (values.length === promises.length) {
            resolve(values)
          }
        }, err= > {
          reject(err)
        })
      })
    })
  }
Copy the code

test

    const p1 = new MyPromise((resolve) = > {
      setTimeout(() = > { resolve(1111)},1000)})const p2 = new MyPromise((resolve, reject) = > {
      setTimeout(() = > { reject(2222)},2000)})const p3 = new MyPromise((resolve) = > {
      setTimeout(() = > { resolve(3333)},3000)
    })
    MyPromise.all([p1, p2, p3]).then(res= > {
      console.log(res)
    }).catch(err= > {
      console.log(err)
    })
Copy the code

Design of allSettled method

  • This approach mainly compensates for the shortcomings of ALL.
  • He will put whatever promise states in Promises into the array, and when all states change, call the promise’s resolve method.
  • Note: He passes the state and value of the promise.
  static allSettled(promises) {
    return new MyPromise((resolve) = > {
      const results = []
      promises.forEach(promise= > {
        promise.then(res= > {
          results.push({ status: PROMISE_STATUS_FULFILLED, value: res})
          if (results.length === promises.length) {
            resolve(results)
          }
        }, err= > {
          results.push({ status: PROMISE_STATUS_REJECTED, value: err})
          if (results.length === promises.length) {
            resolve(results)
          }
        })
      })
    })
  }
Copy the code

test

 const p1 = new MyPromise((resolve) = > {
      setTimeout(() = > { resolve(1111)},1000)})const p2 = new MyPromise((resolve, reject) = > {
      setTimeout(() = > { reject(2222)},2000)})const p3 = new MyPromise((resolve) = > {
      setTimeout(() = > { resolve(3333)},3000)
    })
    MyPromise.allSettled([p1, p2, p3]).then(res= > {
      console.log(res)
    })
Copy the code

Design of race method

  • This method mainly tests which promise state changes first to determine the promise state.
  static race(promises) {
    return new HYPromise((resolve, reject) = > {
      promises.forEach(promise= > {
        // promise.then(res => {
        // resolve(res)
        // }, err => {
        // reject(err)
        // })
        promise.then(resolve, reject)
      })
    })
  } 
Copy the code

test

const p1 = new MyPromise((resolve, reject) = > {
  setTimeout(() = > { reject(1111)},3000)})const p2 = new MyPromise((resolve, reject) = > {
  setTimeout(() = > { reject(2222)},2000)})const p3 = new MyPromise((resolve, reject) = > {
  setTimeout(() = > { reject(3333)},3000)
})


MyPromise.race([p1, p2, p3]).then(res= > {
  console.log("res:", res)
}).catch(err= > {
  console.log("err:", err)
})
Copy the code

The design of any method

  • The any method will wait for a depressing state before deciding the state of the new Promise. If all promises are reject, then you wait until all promises are rejected.

  • If all promises are reject, an AggregateError is reported.througherr.errorsParameters passed when you get all reject.

  • It’s allSettled’s opposite idea.

  static any(promises) {
    const reasons = []
    return new HYPromise((resolve, reject) = > {
      promises.forEach(promise= > {
        promise.then(resolve, err= > {
          reasons.push(err)
          if (reasons.length === promises.length) {
            reject(new AggregateError(reasons))
          }
        })
      })
    })
  }
Copy the code

test

    MyPromise.any([p1, p2, p3]).then(res= > {
      console.log("res:", res)
    }).catch(err= > {
      console.log("err:", err.errors)
    })
Copy the code

The above is all the ideas of handwritten Promise, the main implementation is to figure out the design of then method, get him, get 90% of the Promise. The other way is to understand and use it.

Handwritten Promise in an open class on Everest architecture

His idea of implementation is different from that of Coderwhy. Personally, I think his idea is easy to understand. The main design

  • A resolvePromise is primarily a judgment about the value returned by the then method.
  • The setTimeout in then is used to get the Promise returned by the then method.
class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value= > {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn= >fn()); }};let reject = reason= > {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn= >fn()); }};try{
      executor(resolve, reject);
    } catch(err) { reject(err); }}then(onFulfilled,onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
    onRejected = typeof onRejected === 'function' ? onRejected : err= > { throw err };
    let promise2 = new Promise((resolve, reject) = > {
      if (this.state === 'fulfilled') {
        setTimeout(() = > {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }},0);
      };
      if (this.state === 'rejected') {
        setTimeout(() = > {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }},0);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0);
        });
        this.onRejectedCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0)}); }; });returnpromise2; }}function resolvePromise(promise2, x, resolve, reject){
  if(x === promise2){
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  let called;
  if(x ! =null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') { 
        then.call(x, y= > {
          if(called)return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err= > {
          if(called)return;
          called = true; reject(err); })}else{ resolve(x); }}catch (e) {
      if(called)return;
      called = true; reject(e); }}else{ resolve(x); }}Copy the code

XDM, a lot of thumbs up, hope it helps.