实现 promise

326 阅读4分钟

手写promise经常出现在各大面试题上,本次我尝试着用自己的思路写了一下promise,发现还挺不好写的,前前后后用了4个方案,都没有比较好的通过测试,最后皇天不付有心人,下面是我给出的 promise 思路。

本代码实现了promsie基础的then、catch功能,以及链式调用、值保存、Promise.all、Promise.race、Promise.resolve、Promise.reject 功能,并且通过几个案例进行了测试。

有几个需要注意的地方:

  • 引入了队列缓存then后面的回调,主要应对同一个promise多次调用then的情况
  • 在处理 handleQueue的时候,并没有马上执行,而是使用了 setImmediate() ,在主程序执行完毕以后再执行,主要是为了避免使用 Promsie.resolve()的时候,调用resolve时,队列还没有形成
  • 每一次then或者catch都返回一个新的promise,而这个promise与回调函数维持一个关系,当在执行handleQueue的时候会通过回调找到对应promise,然后执行catch操作或者将promise对应的queue赋值给一个新的promsie,从而执行队列里面的函数。
const {
  setImmediate
} = require('timers')
let Promise1 = function (cb) {
  this.state = 'unfulfilled';
  this.isPromise = true
  this.queue = [] // 通过一个队列缓存then里面的函数,主要应对同一个premise多次调用then的情况
  // 定义resolve函数
  let resolve = (data) => {
    this.state = 'fulfilled'
    // 先将成功数据进行缓存
    this.successData = data
    // 为了防止马上执行的时候队列还没有赋值给新的promise
    this.handleQueue('success', data)
  }

  // 定义reject函数
  let reject = (err) => {
    this.state = 'failed'
    // 先将失败数据进行缓存
    this.errData = err
    this.handleQueue('error', err)
  }
  cb(resolve, reject)
}
Promise1.prototype.handleQueue = function (type, data) {
  setImmediate(() => {
    let current
    // 如果没有queue,那么就直接返回
    if (this.queue.length === 0) {
      return
    }
    current = this.queue.shift()
    while (current) {
      let cb
      if (this.state === 'fulfilled') {
        cb = current && typeof current === 'object' && current.success
      } else {
        cb = current && typeof current === 'object' && current.error
        // 如果没有找到,那么就查找与成功回调关联的promise上的queue
        if (!cb) {
          cb = current.success && current.success.promise && current.success.promise.queue && current.success.promise.queue[0].error
          // 如果找到了,那么就说明这个promise包含catch,将关联promise的queue去掉第一个
          if (cb) {
            current.success.promise.queue.shift()
          }
        }
      }
      // // 如果有成功回调就进行调用
      if (cb) {
        let promise = cb(data)
        // 如果当前返回的是promise,那么将剩下的队列赋值给新的 promise.queue,然后立即结束循环
        if (promise && promise.isPromise) {
          // 将绑定在回调函数上面的 promise.queue 赋值给当前 promise 的 queue
          promise.queue = (current.promise && current.promise.queue) || []
          // return
        } else { // 其余情况,重新生成一个新的promise,这种情况就只有resolve的情况
          let p = Promise1.resolve(promise)
          p.queue = (current.promise && current.promise.queue) || []
        }
      } else { // 否则说明没有设置对应的回调,那么就应该结束后面的执行
        return
      }
      current = this.queue.shift()
    }
  })
}
Promise1.prototype.then = function (fulfilledHandler, errorHandler) {
  if (typeof fulfilledHandler !== 'function') {
    throw new Error('then 的第一个参数必须为函数')
  }
  if (errorHandler && (typeof errorHandler !== 'function')) {
    throw new Error('then 的第二个函数必须为一个函数')
  }
  // 如果已经有数据了,那么直接调用
  if (this.successData) {
    fulfilledHandler(this.successData)
    return
  }
  if (this.errData && errorHandler) {
    errorHandler(this.errData)
    return
  }
  // 将成功函数和失败函数封装成一个对象缓存进
  this.queue.push({
    success: fulfilledHandler,
    error: errorHandler
  })

  const pTemp = Promise1.resolve()
  // 将返回的 promise 与回调函数进行关联
  fulfilledHandler.promise = pTemp
  if (errorHandler) {
    errorHandler.promise = pTemp
  }
  // 返回一个新的 promise
  return pTemp;
};

Promise1.prototype.catch = function (errorHandler) {
  if (typeof errorHandler !== 'function') {
    throw new Error('catch 的第一个参数必须为函数')
  }
  // 如果有数据了就直接返回
  if (this.errData) {
    errorHandler(this.errData)
    return
  }
  this.queue.push({
    error: errorHandler
  })
  const pTemp = Promise1.reject()
  // 将返回的 promise 与回调函数进行关联
  errorHandler.promise = pTemp
  return pTemp;
};

Promise1.resolve = function (val) {
  return new Promise1((resolve) => {
    resolve(val)
  })
}

Promise1.reject = function (val) {
  return new Promise1((resolve, reject) => {
    reject(val)
  })
}

Promise1.all = function (promises) {
  let arr = []
  let length = promises.length
  return new Promise1((resolve, reject) => {
    promises.every((promise, i) => {
      // 这儿兼容 promise 的情况,如果是非 promise。那么就直接返回
      if (promise && typeof promise === 'object' && promise.isPromise) {
        promise.then(data => {
          // console.log('data', data)
          arr[i] = data
          // 这个判断必须在then里面
          length--
          if (length === 0) {
            resolve(arr)
          }
        }, err => {
          reject(err)
        })
        // console.log('这儿是all里面的promise.queue', promise.queue)
      } else {
        arr[i] = promise
        length--
        if (length === 0) {
          resolve(arr)
          return false
        }
      }
      return true
    });
  })
}

Promise1.race = function (promises) {
  return new Promise((resolve, reject) => {
    promises.every((promise, i) => {
      // 返回第一个完成的
      if (promise && typeof promise === 'object' && promise.isPromise) {
        promise.then(data => {
          resolve(data)
        }, err => {
          reject(err)
        })
      } else {
        resolve(promise)
        return false
      }
      // console.log('这儿是race里面的promise.queue', promise.queue)
      return true
    });
  })
}
var p1, p2, p3

// 测试用例1 立即resovle的数据保存
// p1 = Promise1.resolve(123)
// p1.then(res => {
//   console.log('res', res)
// })

// setTimeout(() => {
//   p1.then(res => {
//     console.log('res1', res)
//   })
// }, 1000)

// 测试用例2 延迟resovle的数据保存
// p1 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(111)
//   }, 1000)
// })
// p1.then(res => {
//   console.log('res', res)
// })

// setTimeout(() => {
//   p1.then(res => {
//     console.log('res1', res)
//   })
// }, 2000)

// 测试用例3 立即reject的数据进行保存
// p1 = Promise1.reject(123)
// p1.catch(res => {
//   console.log('err', res)
// })

// setTimeout(() => {
//   p1.catch(res => {
//     console.log('err1', res)
//   })
// }, 1000)

// 测试用例4 延迟reject的数据保存
// p1 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     reject(111)
//   }, 1000)
// })
// p1.catch(err => {
//   console.log('err', err)
// })

// setTimeout(() => {
//   p1.catch(err => {
//     console.log('err1', err)
//   })
// }, 2000)

// 测试用例5 链式使用
// Promise.resolve(111).then(res => {
//   console.log('res', res)
//   return 222
// }, err => {
//   console.log('err', err)
// }).then(res => {
//   console.log('res1', res)
//   return Promise.reject(3333)
// }).then(res => {
//   console.log('res', res)
// }).catch(err => {
//   console.log('err2', err)
// })

// 测试用例6 promise.all / promise.race 同时使用
// p1 = new Promise1((resolve, reject) => {
//   setTimeout(() => {
//     resolve(1111)
//   }, 1000)
// })

// p2 = new Promise1((resolve, reject) => {
//   setTimeout(() => {
//     resolve(2222)
//   }, 2000)
// })

// p3 = new Promise1((resolve, reject) => {
//   setTimeout(() => {
//     resolve(3333)
//   }, 3000)
// })

// Promise1.all([p1, p2, p3]).then(([res1, res2, res3]) => {
//   console.log('res1', res1)
//   console.log('res2', res2)
//   console.log('res3', res3)
// }).catch(err => {
//   console.log('err', err)
// })

// Promise1.race([p1, p2, p3]).then((val) => {
//   console.log('val', val)
// }).catch(err => {
//   console.log('err', err)
// })