Blog icon indicating copy to clipboard operation
Blog copied to clipboard

语法和API - 手写Promise

Open logan70 opened this issue 5 years ago • 0 comments

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

const queueMicrotask = typeof window !== 'undefined' ? window.queueMicrotask : process.nextTick

class Promise {
  constructor(excutor) {
    this.state = PENDING
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []
    excutor(this._resolve.bind(this), this._reject.bind(this))
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e }
    const promise2 = new Promise((resolve, reject) => {
      const next = () => queueMicrotask(() => {
        try {
          const x = this.state === FULFILLED ?
            onFulfilled(this.value) :
            onRejected(this.reason)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })

      if (this.state !== PENDING) return next()
      
      this.onFulfilledCallbacks.push(next)
      this.onRejectedCallbacks.push(next)
    })
    return promise2
  }

  static resolve(value) {
    return new Promise(resolve => resolve(value))
  }

  static reject(reason) {
    return new Promise((_, reject) => reject(reason))
  }

  static race(promises) {
    return new Promise((resolve, reject) => {
      promises.forEach(p => p.then(resolve, reject))
    })
  }

  static all(promises) {
    return new Promise((resolve, reject) => {
      const count = promises.length
      let resultArr = new Array(count)
      let fulfilledCount = 0
      const check = (result, i) => {
        resultArr[i] = result
        fulfilledCount++
        if (fulfilledCount === promises.length) {
          resolve(resultArr)
        }
      }
      promises.forEach((p, i) => {
        p.then(result => check(result, i), reject)
      })
    })
  }

  catch(handler) {
    return this.then(v => v, e => handler(e))
  }

  finally(handler) {
    return this.then(() => handler(), () => handler())
  }

  _resolve(value) {
    if (this.state !== PENDING) return
    this.state = FULFILLED
    this.value = value
    this.onFulfilledCallbacks.forEach(cb => cb(this.value))
  }

  _reject(reason) {
    if (this.state !== PENDING) return
    this.state = REJECTED
    this.reason = reason
    this.onRejectedCallbacks.forEach(cb => cb(this.reason))
  }
}

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)
  }
}

logan70 avatar Dec 24 '19 15:12 logan70