如何实现一个promise

作者 : 开心源码 本文共8855个字,预计阅读时间需要23分钟 发布时间: 2022-05-13 共196人阅读

image

promise 是 ES6 中新添加的一种异步处理方案,在日常开发中也经常能看见它的身影,例如原生的 fetch API 就是基于 promise 实现的。那么 promise 有哪些特性,如何实现一个具备 promise/A+ 规范的 promise 呢?

promise 特性

首先我们整理一下 promise 的少量基本特性和 API,完整的 promise/A+ 规范可以参考 【翻译】Promises/A+规范

  • 状态机
    • 具备 pending、fulfilled、rejected 三个状态
    • 只能由 pending -> fulfilled 和 pending -> rejected 这两种状态变化,且一经改变之后状态不可再变
    • 成功时必需有一个不可改变的值 value,失败时必需有一个不可改变的拒因 reason
  • 构造函数
    • Promise 接受一个函数作为参数,函数拥有两个参数 fulfill 和 reject
    • fulfill 将 promise 状态从 pending 置为 fulfilled,返回操作的结果
    • reject 将 promise 状态从 pending 置为 rejected,返回产生的错误
  • then 方法
    • 接受两个参数 onFulfilled 和 onRejected,分别表示 promise 成功和失败的回调
    • 返回值会作为参数传递到下一个 then 方法的参数中
  • 异步解决
  • 链式调用
  • 其余 API
    • catch、finally
    • resolve、reject、race、all 等

实现

接下来我们逐渐实现一个具备 promise/A+ 规范的 promise

基本实现

先定义一个常量,表示 promise 的三个状态

const STATE = {  PENDING: 'pending',  FULFILLED: 'fulfilled',  REJECTED: 'rejected'}

而后在 promise 中初始化两个参数 value 和 reason,分别表示状态为 fulfill 和 reject 时的值,接着定义两个函数,函数内部升级状态以及相应的字段值,分别在成功和失败的时候执行,而后将这两个函数传入构造函数的函数参数中,如下:

class MyPromise {  constructor(fn) {    // 初始化    this.state = STATE.PENDING    this.value = null    this.reason = null    // 成功    const fulfill = (value) => {      // 只有 state 为 pending 时,才可以更改状态      if (this.state === STATE.PENDING) {        this.state = STATE.FULFILLED        this.value = value      }    }    // 失败    const reject = (reason) => {      if (this.state === STATE.PENDING) {        this.state = STATE.REJECTED        this.reason = reason      }    }    // 执行函数出错时调用 reject    try {      fn(fulfill, reject)    } catch (e) {      reject(e)    }  }}

接下来初步实现一个 then 方法,当当前状态是 fulfulled 时,执行成功回调,当前状态为 rejected 时,执行失败回调:

class MyPromise {  constructor(fn) {    //...  }  then(onFulfilled, onRejected) {    if (this.state === STATE.FULFILLED) {      onFulfilled(this.value)    }    if (this.reason === STATE.REJECTED) {      onRejected(this.reason)    }  }}

这个时候一个简单的 MyPromise 就实现了,但是此时它还只能解决同步任务,对于异步操作却无能为力

异步解决

要想解决异步操作,可以利用队列的特性,将回调函数先缓存起来,等到异步操作的结果返回之后,再去执行相应的回调函数。

具体实现来看,在 then 方法中添加判断,若为 pending 状态,将传入的函数写入对应的回调函数队列;在初始化 promise 时利用两个数组分别保存成功和失败的回调函数队列,并在 fulfill 和 reject 回调中添加它们。如下:

class MyPromise {  constructor(fn) {    // 初始化    this.state = STATE.PENDING    this.value = null    this.reason = null    // 保存数组    this.fulfilledCallbacks = []    this.rejectedCallbacks = []    // 成功    const fulfill = (value) => {      // 只有 state 为 pending 时,才可以更改状态      if (this.state === STATE.PENDING) {        this.state = STATE.FULFILLED        this.value = value        this.fulfilledCallbacks.forEach(cb => cb())      }    }    // 失败    const reject = (reason) => {      if (this.state === STATE.PENDING) {        this.state = STATE.REJECTED        this.reason = reason        this.rejectedCallbacks.forEach(cb => cb())      }    }    // 执行函数出错时调用 reject    try {      fn(fulfill, reject)    } catch (e) {      reject(e)    }  }  then(onFulfilled, onRejected) {    if (this.state === STATE.FULFILLED) {      onFulfilled(this.value)    }    if (this.state === STATE.REJECTED) {      onRejected(this.reason)    }    // 当 then 是 pending 时,将这两个状态写入数组中    if (this.state === STATE.PENDING) {      this.fulfilledCallbacks.push(() => {        onFulfilled(this.value)      })      this.rejectedCallbacks.push(() => {        onRejected(this.reason)      })    }  }}

链式调用

接下来对 MyPromise 进行进一步改造,使其能够支持链式调用,使用过 jquery 等库应该对于链式调用非常熟习,它的原理就是调用者返回它本身,在这里的话就是要让 then 方法返回一个 promise 就可,还有一点就是对于返回值的传递:

class MyPromise {  constructor(fn) {    //...  }  then(onFulfilled, onRejected) {    return new MyPromise((fulfill, reject) => {      if (this.state === STATE.FULFILLED) {        // 将返回值传入下一个 fulfill 中        fulfill(onFulfilled(this.value))      }      if (this.state === STATE.REJECTED) {        // 将返回值传入下一个 reject 中        reject(onRejected(this.reason))      }      // 当 then 是 pending 时,将这两个状态写入数组中      if (this.state === STATE.PENDING) {        this.fulfilledCallbacks.push(() => {          fulfill(onFulfilled(this.value))        })        this.rejectedCallbacks.push(() => {          reject(onRejected(this.reason))        })      }    })  }}

实现到这一步的 MyPromise 已经可以支持异步操作、链式调用、传递返回值,算是一个简易版的 promise,一般来说面试时需要手写一个 promise 时,到这个程度就足够了,完整实现 promise/A+ 规范在面试这样一个较短的时间内也不太现实。

到这一步的完整代码可以参考 promise3.js

promise/A+ 规范

promise/A+ 规范中规定,onFulfilled/onRejected 返回一个值 x,对 x 需要作以下解决:

  • 假如 x 与 then 方法返回的 promise 相等,抛出一个 TypeError 错误
  • 假如 x 是一个 Promise ,则保持 then 方法返回的 promise 的值与 x 的值一致
  • 假如 x 是对象或者函数,则将 x.then 赋值给 then 并调用
    • 假如 then 是一个函数,则将 x 作为作用域 this 调用,并传递两个参数 resolvePromiserejectPromise,假如 resolvePromiserejectPromise 均被调用或者者被调用屡次,则采用初次调用并忽略剩余调用
    • 假如调用 then 方法出错,则以抛出的错误 e 为拒因拒绝 promise
    • 假如 then 不是函数,则以 x 为参数执行 promise
  • 假如 x 是其余值,则以 x 为参数执行 promise

接下来对上一步实现的 MyPromise 进行进一步优化,使其符合 promise/A+ 规范:

class MyPromise {  constructor(fn) {    //...  }  then(onFulfilled, onRejected) {    const promise2 = new MyPromise((fulfill, reject) => {      if (this.state === STATE.FULFILLED) {        try {          const x = onFulfilled(this.value)          generatePromise(promise2, x, fulfill, reject)        } catch (e) {          reject(e)        }      }      if (this.state === STATE.REJECTED) {        try {          const x = onRejected(this.reason)          generatePromise(promise2, x, fulfill, reject)        } catch (e) {          reject(e)        }      }      // 当 then 是 pending 时,将这两个状态写入数组中      if (this.state === STATE.PENDING) {        this.fulfilledCallbacks.push(() => {          try {            const x = onFulfilled(this.value)            generatePromise(promise2, x, fulfill, reject)          } catch(e) {            reject(e)          }        })        this.rejectedCallbacks.push(() => {          try {            const x = onRejected(this.reason)            generatePromise(promise2, x, fulfill, reject)          } catch (e) {            reject(e)          }        })      }    })    return promise2  }}

这里将解决返回值 x 的行为封装成为了一个函数 generatePromise,实现如下:

const generatePromise = (promise2, x, fulfill, reject) => {  if (promise2 === x) {    return reject(new TypeError('Chaining cycle detected for promise'))  }  // 假如 x 是 promise,调用它的 then 方法继续遍历  if (x instanceof MyPromise) {    x.then((value) => {      generatePromise(promise2, value, fulfill, reject)    }, (e) => {      reject(e)    })  } else if (x != null && (typeof x === 'object' || typeof x === 'function')) {    // 防止重复调用,成功和失败只能调用一次    let called;    // 假如 x 是对象或者函数    try {      const then = x.then      if (typeof then === 'function') {        then.call(x, (y) => {          if (called) return;          called = true;          // 说明 y是 promise,继续遍历          generatePromise(promise2, y, fulfill, reject)        }, (r) => {          if (called) return;          called = true;          reject(r)        })      } else {        fulfill(x)      }    } catch(e) {      if (called) return      called = true      reject(e)    }  } else {    fulfill(x)  }}

promise/A+ 规范中还规定,对于 promise2 = promise1.then(onFulfilled, onRejected)

  • onFulfilled/onRejected 必需异步调用,不能同步
  • 假如 onFulfilled 不是函数且 promise1 成功执行, promise2 必需成功执行并返回相同的值
  • 假如 onRejected 不是函数且 promise1 拒绝执行, promise2 必需拒绝执行并返回相同的拒因

对于 then 方法做最后的完善,添加 setTimeout 模拟异步调用,添加对于 onFulfilled 和 onRejected 方法的判断:

class MyPromise {  constructor(fn) {    //...  }  then(onFulfilled, onRejected) {    // 解决 onFulfilled 和 onRejected    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value    onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e }    const promise2 = new MyPromise((fulfill, reject) => {      // setTimeout 宏任务,确保onFulfilled 和 onRejected 异步执行      if (this.state === STATE.FULFILLED) {        setTimeout(() => {          try {            const x = onFulfilled(this.value)            generatePromise(promise2, x, fulfill, reject)          } catch (e) {            reject(e)          }        }, 0)      }      if (this.state === STATE.REJECTED) {        setTimeout(() => {          try {            const x = onRejected(this.reason)            generatePromise(promise2, x, fulfill, reject)          } catch (e) {            reject(e)          }        }, 0)      }      // 当 then 是 pending 时,将这两个状态写入数组中      if (this.state === STATE.PENDING) {        this.fulfilledCallbacks.push(() => {          setTimeout(() => {            try {              const x = onFulfilled(this.value)              generatePromise(promise2, x, fulfill, reject)            } catch(e) {              reject(e)            }          }, 0)        })        this.rejectedCallbacks.push(() => {          setTimeout(() => {            try {              const x = onRejected(this.reason)              generatePromise(promise2, x, fulfill, reject)            } catch (e) {              reject(e)            }          }, 0)        })      }    })    return promise2  }}

实现 promise/A+ 规范的 promise 完整代码可以参考 promise4.js

如何知道你实现的 promise 能否遵循 promise/A+ 规范呢?可以利用 promises-aplus-tests 这样一个 npm 包来进行相应测试

其余 API

这里对其余常用的 promise API 进行了实现

catch、finally

class MyPromise {  constructor(fn) {    //...  }  then(onFulfilled, onRejected) {    //...  }  catch(onRejected) {    return this.then(null, onRejected)  }  finally(callback) {    return this.then(callback, callback)  }}

Promise.resolve

返回一个 resolved 状态的 Promise 对象

MyPromise.resolve = (value) => {  // 传入 promise 类型直接返回  if (value instanceof MyPromise) return value  // 传入 thenable 对象时,立即执行 then 方法  if (value !== null && typeof value === 'object') {    const then = value.then    if (then && typeof then === 'function') return new MyPromise(value.then)  }  return new MyPromise((resolve) => {    resolve(value)  })}

Promise.reject

返回一个 rejected 状态的 Promise 对象

MyPromise.reject = (reason) => {  // 传入 promise 类型直接返回  if (reason instanceof MyPromise) return reason  return new MyPromise((resolve, reject) => {    reject(reason)  })}

Promise.race

返回一个 promise,一旦迭代器中的某个 promise 状态改变,返回的 promise 状态随之改变

MyPromise.race = (promises) => {  return new MyPromise((resolve, reject) => {    // promises 可以不是数组,但必需存在 Iterator 接口,因而采用 for...of 遍历    for(let promise of promises) {      // 假如当前值不是 Promise,通过 resolve 方法转为 promise      if (promise instanceof MyPromise) {        promise.then(resolve, reject)      } else {        MyPromise.resolve(promise).then(resolve, reject)      }    }  })}

Promise.all

返回一个 promise,只有迭代器中的所有的 promise 均变为 fulfilled,返回的 promise 才变为 fulfilled,迭代器中出现一个 rejected,返回的 promise 变为 rejected

MyPromise.all = (promises) => {  return new MyPromise((resolve, reject) => {    const arr = []    // 已返回数    let count = 0    // 当前索引    let index = 0    // promises 可以不是数组,但必需存在 Iterator 接口,因而采用 for...of 遍历    for(let promise of promises) {      // 假如当前值不是 Promise,通过 resolve 方法转为 promise      if (!(promise instanceof MyPromise)) {        promise = MyPromise.resolve(promise)      }      // 使用闭包保证异步返回数组顺序      ((i) => {        promise.then((value) => {          arr[i] = value          count += 1          if (count === promises.length || count === promises.size) {            resolve(arr)          }        }, reject)      })(index)      // index 递增      index += 1    }  })}

Promise.allSettled

只有等到迭代器中所有的 promise 都返回,才会返回一个 fulfilled 状态的 promise,并且返回的 promise 状态总是 fulfilled,不会返回 rejected 状态

MyPromise.allSettled = (promises) => {  return new MyPromise((resolve, reject) => {    const arr = []    // 已返回数    let count = 0    // 当前索引    let index = 0    // promises 可以不是数组,但必需存在 Iterator 接口,因而采用 for...of 遍历    for(let promise of promises) {      // 假如当前值不是 Promise,通过 resolve 方法转为 promise      if (!(promise instanceof MyPromise)) {        promise = MyPromise.resolve(promise)      }      // 使用闭包保证异步返回数组顺序      ((i) => {        promise.then((value) => {          arr[i] = value          count += 1          if (count === promises.length || count === promises.size) {            resolve(arr)          }        }, (err) => {          arr[i] = err          count += 1          if (count === promises.length || count === promises.size) {            resolve(arr)          }        })      })(index)      // index 递增      index += 1    }  })}

本文如有错误,欢迎批评指正~

参考

  • MDN-Promise
  • ECMAScript 6 入门-Promise 对象
  • 【翻译】Promises/A+规范
  • 完整的示例和测试代码:github/lvqq/promise
说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 如何实现一个promise

发表回复