(给前端大全加星标,提升前端技能)

转自:Col0ring

juejin.cn/post/6886360224308035598

写在前面

没错,这又是一篇关于手写 Promise 的文章,想必大家已经看过很多相关 Promise 的文章,关于一些 Promise 出现原因等问题我就不详细说了,网上有很多资料。这次我们使用 typescript,从 ES6 中的 Promise 类型定义入手,分析 Promise 及相关方法的传入参数和返回值,手写一个 typescript 版本的 Promise。

Promise/A+ 规范

Promise/A+ 规范是业内所有的 Promise 类库的统一规范,我们要写的 Promise 也要符合这一规范(英文文档请查看 Promises/A+:https://promisesaplus.com/,相关中文翻译 Promise A+ 规范:http://malcolmyu.github.io/malnote/2015/06/12/Promises-A-Plus/,感谢译者)。

由于内容过多,在下面的编码中会一一进行实现,这里先提出几个术语:

  • 解决(fulfill):指一个 promise 成功时进行的一系列操作,如状态的改变、回调的执行。虽然规范中用 fulfill 来表示解决,但在后世的 promise 实现多以 resolve 来指代之。
  • 拒绝(reject):指一个 promise 失败时进行的一系列操作。
  • 终值(eventual value):所谓终值,指的是 promise 被解决(fulfill)时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)
  • 据因(reason):也就是拒绝原因,指在 promise 被拒绝(reject) 时传递给拒绝回调的值。

值得注意的是,核心的 Promises/A+ 规范不设计如何创建、解决和拒绝 promise,而是专注于提供一个通用的 then 方法。所以,完成了对于then方法的交互,其实也就基本完成了对于 Promises/A+ 规范的实现。

实现 Promise

基本功能

首先我们要实现 Promise 的基本功能,传入生成器,reslovereject函数的执行以及调用then函数对于值的基本获取,先来看看构造函数的类型定义:

// 这是类本身,还有 all, race 等方法在这里面定义interface PromiseConstructor {    /**     * A reference to the prototype.     */    readonly prototype: Promise<any>;    /**     * Creates a new Promise.     * @param executor A callback used to initialize the promise. This callback is passed two arguments:     * a resolve callback used to resolve the promise with a value or the result of another promise,     * and a reject callback used to reject the promise with a provided reason or error.     */   // 这里就是重点部分了    new (executor: (resolve: (value?: T | PromiseLike) => void, reject: (reason?: any) => void) => void): Promise<T>;  // ...}

上面我们会看到两个其余的接口类型,分别是PromisePromiseLike,其中Promise就是实例对象的相关属性接口:

// 这时实例对象,下面只是 ES2015 的接口属性,因为后续 Promise 做过更新,后续会说明更多实例属性interface Promise {/**     * Attaches callbacks for the resolution and/or rejection of the Promise.     * @param onfulfilled The callback to execute when the Promise is resolved.     * @param onrejected The callback to execute when the Promise is rejected.     * @returns A Promise for the completion of which ever callback is executed.     */    then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise<TResult1 | TResult2>;    //...}

PromiseLike的接口在下面:

interface PromiseLike {/**     * Attaches callbacks for the resolution and/or rejection of the Promise.     * @param onfulfilled The callback to execute when the Promise is resolved.     * @param onrejected The callback to execute when the Promise is rejected.     * @returns A Promise for the completion of which ever callback is executed.     */    then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): PromiseLike<TResult1 | TResult2>;}

可以看出PromiseLike接口定义的就是一个拥有then()方法的对象(官方的叫法是 thenable),只要有then()方法就会将其当做一个Promise实例看待。

// 试验一下new Promise((resolve) => {  resolve({    prop: 'common property',    // 这里我们自己构造了个 then 方法,Promise 会自动为 then 方法 reslove 和 reject 函数    then(reslove2: any) {      reslove2('promiselike')    }  })}).then((res) => {  // 果然,被当做成了 Promise  console.log(res) // promiselike})

需要注意的是,Promise 内部的回调函数的异步执行机制是使用的微任务,而我们所使用的环境中并没有为我们提供微任务的相关 api,所以代码中都是使用setTimeout进行异步模拟,将回调直接推入到事件环的最后。

如果对事件环与微任务不太了解,可以查看下这篇文章 彻底搞懂 JS 事件轮询:https://juejin.im/post/6844904198581010439。

下面是代码实现:

// 创建一枚举类型保存响应状态的变量enum Status {  PENDING = 'pending',  FULFILLED = 'fulfilled',  REJECTED = 'rejected'}

// 将需要类型提出来type Resolve = (value: T | PromiseLike) => voidtype Reject = (reason?: any) => voidtype Executor = (resolve: Resolve, reject: Reject) => voidtype onFulfilled =  | ((value: T) => TResult1 | PromiseLike)  | undefined  | nulltype onRejected<TResult2> =  | ((reason: any) => TResult2 | PromiseLike)  | undefined  | null/*  将判断是否为 thenable 单独提出来,减少代码冗余,不然每次都需要使用: ((typeof value === 'object' && value !== null) ||typeof value === 'function') && typeof (value as PromiseLike).then === 'function'  来进行判断,同时也有更好的 typescript 提示*/function isPromise(value: any): value is PromiseLike<any> {return (    ((typeof value === 'object' && value !== null) ||typeof value === 'function') &&typeof value.then === 'function')}class MyPromise<T> {  // 刚开始的状态status: Status = Status.PENDING  // 保存当前 Promise 的终值,这里让它一定会有值private value!: T  // 保存当前 Promise 的据因private reason?: anyprivate onFulfilledCallback: (() => void)[] = [] //成功的回调private onRejectedCallback: (() => void)[] = [] //失败的回调constructor(executor: Executor) {try {      // 防止 this 丢失executor(this._resolve.bind(this), this._reject.bind(this))    } catch (e) {      // 出错直接 rejectthis._reject(e)    }  }private _resolve(value: T | PromiseLike) {try{      // 模拟微任务异步setTimeout(() => {// 判断是否是个 thenable 对象,如果是,我们直接取 pending 结束后的值if (isPromise(value)) {// 再次将内部的 resolve 和 reject 函数传入          value.then(this._resolve.bind(this), this._reject.bind(this))return        }// 如果是 pending 状态就变为 fulfilledif (this.status === Status.PENDING) {this.status = Status.FULFILLED// 这里的 value 类型只会是 Tthis.value = value// resolve 后执行 .then 时传入的回调this.onFulfilledCallback.forEach((fn) => fn())        }      })    }catch(err){      // 捕获如果传入的是 Promise 时在内部抛出错误后的捕获this._reject(err)    }  }  // 内部的 reject 函数,就是我们实例 Promise 传入给用户调用的 rejectprivate _reject(reason: any) {    // 大体用法同上,这里不用进行值穿透,所以不用判断是否为 Promise 对象了setTimeout(() => {if (this.status === Status.PENDING) {this.status = Status.REJECTEDthis.reason = reasonthis.onRejectedCallback.forEach((fn) => fn())      }    })  }public then<TResult1 = T, TResult2 = never>(    onfulfilled?: onFulfilled,    onrejected?: onRejected): MyPromise<TResult1 | TResult2> {    //  关于 onfulfilled 与 onrejected 如果没有传我们需要进行值的透传,但是在基本功能的实现中我们先不管这个问题,默认一定会传入函数    // 判断当前状态,如果是异步 reslove 或 reject,那么此时的 status 还是 pendingif (this.status === Status.FULFILLED) {setTimeout(() => {        onfulfilled!(this.value)      })    }if (this.status === Status.REJECTED) {setTimeout(() => {        onrejected!(this.reason)      })    }if (this.status === Status.PENDING) {      // 如果为 pending,需要将 onFulfilled 和 onRejected 函数都存放起来,状态确定后再依次执行      // 执行回调的时候有 setTimeout,这里就不加了this.onFulfilledCallback.push(() => {        onfulfilled!(this.value)      })this.onRejectedCallback.push(() => {        onrejected!(this.reason)      })    }    // 链式调用,这段代码现在可以直接无视,为了不让 ts 类型报错加的,因为 .then 返回一个的 Promise 的值是依赖上一个 Promise 的状态和结果的return new MyPromise(() => {})  }}

OK,上面已经完成了一个只有一条链的 Promise,下面做一下测试:

// 同步new MyPromise((reslove, reject) => {  reslove('success')}).then(  (res) => {    console.log(res) // success  },  (err) => {    console.log(err)  })

// 异步new MyPromise((reslove, reject) => {  setTimeout(() => {    reslove('timeout success')  }, 2000)}).then(  (res) => {    console.log(res) // timeout success  },  (err) => {    console.log(err)  })

结果为立刻打印success,两秒后打印timeout success,符合我们的预期。

then 的深入(重点)

就如前面所说,Promise/A+ 规范的整个核心都在于对于then方法的处理。并且还有第三方测试库 promises-aplus-tests:https://github.com/promises-aplus/promises-tests 测试我们所写的 Promise 是否符合规范,我们后面也会用使用这个测试库进行测试。

链式调用

要实现then的链式调用,需要返回一个新的 Promise,同时不管在then中回调函数onfulfilledonrejected返回了什么值,都可以在这个新的 Promise 的then方法的回调函数参数中得到。

我们用x来作为then方法中传入的onfulfilledonrejected的返回值,用promise来表示then方法返回的那个新的 Promise,依据 Promise/A+ 规范,我们应该对这段解决过程 [[Resolve]](promise, x) 做如下操作:

  • xpromise 相等:如果 promisex 指向同一对象,以 TypeError 为据因拒绝执行 promise
  • x 为 Promise:如果 x 为 Promise ,则使 promise 接受 x 的状态。
    • 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
    • 如果 x 处于执行态,用相同的值执行 promise
    • 如果 x 处于拒绝态,用相同的据因拒绝 promise
  • x 为对象或函数
    • 如果 x 为对象或者函数:1.  把 x.then 赋值给 then2.  如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise3.  如果then是函数,将x作为函数的作用域this调用之。传递两个回调函数作为参数,第一个参数叫做resolvePromise,第二个参数叫做rejectPromise
  1. 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)(就是继续递归这段解决过程)
  2. 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
  3. 如果 resolvePromiserejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
  1. 如果调用then方法抛出了异常e:
  • 如果 resolvePromiserejectPromise 已经被调用,则忽略之
  • 否则以 e 为据因拒绝 promise
    4.  如果 then 不是函数,以 x 为参数执行 promise
  • 如果 x 不为对象或者函数,以 x 为参数执行 promise

大体的流程图:


虽然上面看着有点多,其实大体来说就是让我们符合以下规则:

  • 如果 then 中的回调函数返回一个值(非 Promise 实例或是 thenable 对象)或没有返回值(也就是返回 undefined),那么 then 返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数(onfulfilled)的参数值。

    new Promise<void>((reslove) => {reslove()}).then(() => {return 'success'}).then((res) => {console.log(res) // success})复制代码
  • 如果 then 中的回调函数抛出一个错误,那么 then 返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数(onrejected)的参数值。

    new Promise<void>((reslove) => {reslove()}).then(() => {throw new Error('error message')}).then(  () => {},  (err) => {console.log(err) // Error: error message  })

    循环返回 Promise 抛出错误就像下面这样 :

    const promise2: Promise<any> = new Promise<void>((reslove) => {reslove()}).then(() => {return promise2})promise2.then(() => {}, console.log) // [TypeError: Chaining cycle detected for promise #<Promise>]
  • 如果 then 中的回调函数返回一个已经是接受状态(fulfilled)的 Promise(我们这里暂且叫做promise1),那么 then 返回的 Promise 也会成为接受状态,并且将promise1then的回调函数的参数值,作为该被返回的 Promise 的接受状态回调函数的参数值。

    new Promise<void>((reslove) => {reslove()}).then(() => {return Promise.reslove('success')}).then((res) => {console.log(res) // success})
  • 如果 then 中的回调函数返回一个已经是拒绝状态(rejected)的 Promise(这里我们暂且叫做promise2),那么 then 返回的 Promise 也会成为拒绝状态,并且将promise2then的回调函数的参数值,作为该被返回的 Promise 的拒绝状态回调函数的参数值。

    new Promise<void>((reslove) => {reslove()}).then(() => {return new Promise.reject('error message')}).then(  () => {},  (err) => {console.log(err) // error message  })
  • 如果 then 中的回调函数返回一个未定状态(pending)的 Promise(这里我们暂且叫做promise3),那么 then 返回 Promise 的状态也是未定的,并且它的终态与promise3的终态相同。同时它变为终态时调用then的回调函数参数与promise3变为终态时的回调函数的参数是相同的。

    new Promise<void>((reslove) => {reslove()}).then(() => {return new Promise((reslove, reject) => {     setTimeout(()=>{      reslove('delay')      }, 2000)  })}).then(  res => {console.log(res) // 两秒后打印 delay  })

好了,照着上面的解决过程 ,我们来写一下这个处理函数:

function resolvePromise<T>(  promise2: MyPromise,  x: T | PromiseLike,  resolve: Resolve,  reject: Reject) {  // 不能引用同一个对象,不然会无限循环的  if (promise2 === x) {    const e = new TypeError(      'TypeError: Chaining cycle detected for promise #'    )    // 清空栈信息,不太清楚为什么 Promise 要清除这个,先不管了,继续往下    e.stack = ''    // 直接进入错误的回调    return reject(e)  }  let called = false // 防止多次调用

  // 如果 x 为 Promise,通过上面的知识我们知道判断是否是个 Promise 或者像 Promise 我们是判断一个对象是否有 then 方法,可以发现在下面判断是否是对象或者函数中也有相同的判断,所以这里我们可以直接省略

  // 如果 x 是对象或函数  if ((typeof x === 'object' && x != null) || typeof x === 'function') {    try {      /*       存储了一个指向 x.then 的引用,然后测试并调用该引用,以避免多次访问 x.then 属性。这种预防措施确保了该属性的一致性,因为其值可能在检索调用时被改变。      注:这里可以用我们封装的判断方法 isPromise 判断,但是既然跟着解决过程走,那么还是老老实实操作一下吧      */      // 手动转一下类型      const then = (x as PromiseLike).thenif (typeof then === 'function') {// 这里其实就是调用传入的 Promise 的 then 方法,下面代码就是执行了 x.then(()=>{},()=>{})        then.call(          x,(y) => {if (called) return            called = true// 如果是 Promise,我们应该递归地获取到最终状态的值,传入相同的处理函数,不论是成功还是失败都能直接抛出到最外层            resolvePromise(promise2, y, resolve, reject)          },(r) => {if (called) return            called = true// 如果传入的 Promise 被拒绝,直接抛出到最外层            reject(r)          }        )      } else {// 不是 Promise 对象,当做普通值处理        resolve(x)      }    } catch (e) {// 如果中间有错误。直接变为拒绝态// 但是如果出现错误之前已经改变了状态,那么久不用管if (called) return      called = true      reject(e)    }  } else {// 普通值处理    resolve(x)  }}

可以将上面的处理过程的代码实现与描述一步步带入查看,基本上都是能吻合的。

下面将其带入到then中:

class MyPromise {// ...public then(    onfulfilled?: onFulfilled,    onrejected?: onRejected  ): MyPromise {// 现在我们将这个新生成的 Promise 和现在的 Promise 相互联系const promise2 = new MyPromise((resolve, reject) => {if (this.status === Status.FULFILLED) {        setTimeout(() => {try {//  获取到 x,然后与要返回的 Promise 产生联系let x = onfulfilled!(this.value)            resolvePromise(promise2, x, resolve, reject)          } catch (e) {            reject(e)          }        })      }if (this.status === Status.REJECTED) {        setTimeout(() => {try {//  获取到 x,然后与要返回的 Promise 产生联系let x = onrejected!(this.reason)            resolvePromise(promise2, x, resolve, reject)          } catch (e) {            reject(e)          }        })      }if (this.status === Status.PENDING) {// 如果为 pending,需要将 onFulfilled 和 onRejected 函数都存放起来,状态确定后再依次执行// 执行回调的时候有 setTimeout,这里就不加了this.onFulfilledCallback.push(() => {try {let x = onfulfilled!(this.value)            resolvePromise(promise2, x, resolve, reject)          } catch (e) {            reject(e)          }        })this.onRejectedCallback.push(() => {try {let x = onrejected!(this.reason)            resolvePromise(promise2, x, resolve, reject)          } catch (e) {            reject(e)          }        })      }    })return promise2  }  //...}

测试一下:

new MyPromise<void>((resolve) => {  resolve()})  .then(() => {return 'step1'  })  .then((res) => {return res + ':' + 'step2'  })  .then((res) => {console.log(res) // step1:step2  })

很好,完美符合预期。

值的穿透

之前我们使用then时都是假设我们一定会向then中传入回调函数,但是事实上在 Promise/A+ 规范中两个回调函数都是可以缺省的,这也是为什么我会在后面加上?。当我们不向其传入回调函数时,此时就会触发值的穿透效果。

// 就像下面这样new Promise((reslove) => {  reslove('hello')})  .then()  .then()  .then()  .then((res) => {    console.log(res) // 'hello'  })

所以我们需要改造一下我们的then函数,改造方法其实非常简单:

class MyPromise {// ...public then(    onfulfilled?: onFulfilled,    onrejected?: onRejected  ): MyPromise {// 如果传入的不是函数,就进行值的穿透,成功回调是返回相同的值,失败的回调是直接抛出错误// 注意这里不能直接给上面传入的参数添加默认值,因为需要判断是否是函数const onfulfilledFn =typeof onfulfilled === 'function'        ? onfulfilled        : (v: T | TResult1) => v as TResult1const onrejectedFn =typeof onrejected === 'function'        ? onrejected        : (e: any) => {throw e          }// 将下面的 onfulfilled 改成 onfulfilledFn,onrejected 改成 onrejectedFn 就行了// 现在我们将这个新生成的 Promise 和现在的 Promise 相互联系const promise2 = new MyPromise((resolve, reject) => {if (this.status === Status.FULFILLED) {        setTimeout(() => {try {//  获取到 x,然后与要返回的 Promise 产生联系let x = onfulfilledFn(this.value)            resolvePromise(promise2, x, resolve, reject)          } catch (e) {            reject(e)          }        })      }if (this.status === Status.REJECTED) {        setTimeout(() => {try {//  获取到 x,然后与要返回的 Promise 产生联系let x = onrejectedFn(this.reason)            resolvePromise(promise2, x, resolve, reject)          } catch (e) {            reject(e)          }        })      }if (this.status === Status.PENDING) {// 如果为 pending,需要将 onFulfilled 和 onRejected 函数都存放起来,状态确定后再依次执行// 执行回调的时候有 setTimeout,这里就不加了this.onFulfilledCallback.push(() => {try {let x = onfulfilledFn(this.value)            resolvePromise(promise2, x, resolve, reject)          } catch (e) {            reject(e)          }        })this.onRejectedCallback.push(() => {try {let x = onrejectedFn(this.reason)            resolvePromise(promise2, x, resolve, reject)          } catch (e) {            reject(e)          }        })      }    })return promise2  }  //...}

好了,看到了这里,恭喜你,已经完成了 Promise 最重要的部分,后续的所有 api 基本都是围绕着上面所写的部分展开的。

规范测试

使用我们前面提到的测试 Promise 规范的第三方库来进行测试:

npm install promises-aplus-tests -D# oryarn add promises-aplus-tests -D复制代码
// 在文件末尾加上

// 忽略 typescript 校验// @ts-ignoreMyPromise.defer = MyPromise.deferred = function () {  let dfd: any = {}  dfd.promise = new MyPromise((resolve, reject) => {    dfd.resolve = resolve    dfd.reject = reject  })  return dfd}

export = MyPromise

然后使用tsc命令将ts文件编译,运行npx promises-aplus-tests 编译后的js文件位置


遗憾的是,我们的 Promise 并没有通过所有的测试,但是我们可以看到这 16 个未通过测试的报错都相同,调用处理的回调超时(超过了 200 ms 的延时),我的个人理解是由于setTimeout这个 api 不能完全模拟微任务而造成的延时效果(说错了请大佬们轻点喷)。

如果要单看测试结果来说,想全部通过测试我们可以把最开始定义的 Promise 内部的reslove函数做一个修改:

class MyPromise{  private _resolve(value: T | PromiseLike) {try{      setTimeout(() => {/*         删除下面这段代码就可通过全部测试:          if (isPromise(value)) {            value.then(this._resolve.bind(this), this._reject.bind(this))            return          }        */if (this.status === Status.PENDING) {this.status = Status.FULFILLEDthis.value = value as T // 强制转换类型this.onFulfilledCallback.forEach((fn) => fn())        }      })    }catch(err){this._reject(err)    }  }}

当然,虽然通过了全部测试,但是很明显并不是符合预期的结果,当我们使用一个PromiseLike对象时与真实的 Promise 结果并不一致:

// 还是用最开始的那个例子new Promise((resolve) => {  resolve({    prop: 'common property',    then(reslove2: any) {      reslove2('promiselike')    }  })}).then((res) => {  // 真实的 Promise 这里是 promiselike  console.log(res) // { prop: 'common property', then: [Function: then] } })

所以,就结果而言,也算是基本合格吧。

Promise 的拓展方法

在前面我们已经完成了 Promise 的核心部分,现在可以依靠之前的代码再次完善我们的 Promise。

注: 在 Promise 的接口定义里,所有的实例方法都是定义在Promise接口中的,所有的静态方法都是定义在PromiseConstructor接口中的。

Promise.prototype.catch

Promise.prototype.catch(onrejected) 方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected)相同。

接口类型:

interface Promise {/**     * Attaches a callback for only the rejection of the Promise.     * @param onrejected The callback to execute when the Promise is rejected.     * @returns A Promise for the completion of the callback.     */catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise<T | TResult>;}

实现代码:

interface MyPromise {  // 事实上它内部就是这样调用的  public catch(    onrejected?: onRejected  ): MyPromise {return this.then(null, onrejected)  }}

Promise.resolve

Promise.resolve(value) 方法返回一个以给定值解析后的 Promise 对象。

  • 如果这个值是一个 Promise 实例 ,那么将返回这个 Promise 实例。
  • 如果这个值是thenable,会一直跟随thenable拿到它的最终状态。
  • 如果都不是上面的值,返回的 Promise 实例将用这个值作为成功状态的终值。

接口类型:

interface PromiseConstructor {   // 可以看到有两种函数体,所以我们需要进行函数重载的定义     /**     * Creates a new resolved promise.     * @returns A resolved promise.     */    resolve(): Promise<void>;

    /**     * Creates a new resolved promise for the provided value.     * @param value A promise.     * @returns A promise whose internal state matches the provided promise.     */    resolve(value: T | PromiseLike): Promise;}

实现代码:

interface MyPromise {  // 函数重载  static resolve(): MyPromise<void>  static resolve(value: T | PromiseLike): MyPromise// 最后的函数实体需要同时支持上面两种函数重载的类型,所以我们变成可选值static resolve(value?: T | PromiseLike): MyPromise {// 如果是 Promise,直接返回当前 Promiseif (value instanceof MyPromise) {return value    }return new MyPromise((resolve) => {// 我们在内部已经做了对 thenable 的处理了,所以直接 reslove// 因为必须传值,所以这里就强制推断了      resolve(value!)    })  }}

Promise.reject

Promise.reject(reason) 方法返回一个带有拒绝原因的Promise对象。

接口类型:

interface PromiseConstructor {    /**     * Creates a new rejected promise for the provided reason.     * @param reason The reason the promise was rejected.     * @returns A new rejected Promise.     */    reject(reason?: any): Promise;}

实现代码:

interface MyPromise {  static reject(reason?: any): MyPromise {// 不需要额外判断return new MyPromise((resolve, reject) => {      reject(reason)    })  }}

Promise.all

Promise.all(iterable)方法接收一个iterable对象,返回一个 Promise 实例,此实例在 iterable 参数内所有的 thenable 状态都为fulfilled或参数中不包含 thenable 时状态为fulfilled,并且reslove一个包含了所有传入thenablereslove值的数组。如果参数内的 thenable 有一个状态为rejected,此实例状态也为rejected,并且reject第一个失败 thenable 的结果。否者如果有thenable的状态为pending,此实例的状态也为pending

接口定义:

interface PromiseConstructor {      /**     * Creates a Promise that is resolved with an array of results when all of the provided Promises     * resolve, or rejected when any Promise is rejected.     * @param values An array of Promises.     * @returns A new Promise.     */    all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike, T10 | PromiseLike]): Promise;    all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike]): Promise;    all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike]): Promise;    all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike]): Promise;    all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike]): Promise;    all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike]): Promise;    all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike]): Promise;    all(values: readonly [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike]): Promise;    all(values: readonly [T1 | PromiseLike, T2 | PromiseLike]): Promise;    all(values: readonly (T | PromiseLike)[]): Promise;// 看着有点多,其实上面都是表示传入参数是一个数组的情况,这样写是因为传入的 Promise 中的 T 可能不同而重载不同元组类型// see: lib.es2015.iterable.d.ts    all(values: Iterable>): Promise;}

在类型定义中我们可以看到,当我们传入给Promise.all()一个迭代器(Iterable)的时候参数也是正确的,而数组本质也是一个迭代器,所以我们的编码操作可以完全围绕着迭代器进行展开。

实现代码:

interface MyPromise {  static all(  values: readonly [   T1 | PromiseLike,   T2 | PromiseLike,   T3 | PromiseLike,   T4 | PromiseLike,   T5 | PromiseLike,   T6 | PromiseLike,   T7 | PromiseLike,   T8 | PromiseLike,   T9 | PromiseLike,   T10 | PromiseLike   ]  ): MyPromise// .....太占篇幅,省略了static all(values: Iterable>): MyPromise// 我们这里的实际实现也完全按照按照迭代器来实现就行了static all(values: Iterable>): MyPromise {return new MyPromise((resolve, reject) => {// PromiseLike 对象会跟踪转换为 Tconst resultArr: T[] = []//  判断是否已经全部完成了const doneArr: boolean[] = []// 获取迭代器对象let iter = values[Symbol.iterator]()// 获取值 {value:xxx, done: false}let cur = iter.next()// 判断迭代器是否迭代完毕同时将最后得到的值放入结果数组中const resolveResult = (value: T, index: number, done?: boolean) => {        resultArr[index] = value        doneArr[index] = trueif (done && doneArr.every((item) => item)) {          resolve(resultArr)        }      }for (let i = 0; !cur.done; i++) {const value = cur.value        doneArr.push(false)        cur = iter.next()if (isPromise(value)) {          value.then((value: T) => {            resolveResult(value, i, cur.done)          }, reject)        } else {          resolveResult(value, i, cur.done)        }      }    })  }

Promise.race

Promise.race(iterable) 方法接收一个iterable对象,返回一个 Promise,一旦迭代器中的某个thenable的状态变为fulfiledrejected,该实例的状态就会变成fulfiledrejected

接口定义:

interface PromiseConstructor {    /**     * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved     * or rejected.     * @param values An array of Promises.     * @returns A new Promise.     */    race(values: readonly T[]): Promiseextends PromiseLike ? U : T>;// see: lib.es2015.iterable.d.ts    race(values: Iterable): Promiseextends PromiseLike ? U : T>;}

代码实现:

class MyPromise {  static race(    values: Iterable  ): MyPromiseextends PromiseLike ? U : T>static race(    values: readonly T[]  ): MyPromiseextends PromiseLike ? U : T>// 还是直接使用迭代器static race(    values: Iterable  ): MyPromiseextends PromiseLike ? U : T> {return new MyPromise((resolve, reject) => {const iter = values[Symbol.iterator]()let cur = iter.next()while (!cur.done) {const value = cur.value          cur = iter.next()if (isPromise(value)) {            value.then(resolve, reject)          } else {// 普通值,这时的值为 T,但是 Typescript 无法再深度判断了,需要自己手动转换            resolve(value as T extends PromiseLike ? U : T)          }        }      })  }}

Promise.prototype.finally

ES2018提出

Promise.prototype.finally(onfinally) 方法返回一个新的 Promise,并且该 Promise 的状态为 Promise 链条中前一个 Promise 的状态。在上一个 Promise 结束时,无论结果状态是fulfilled或者是rejected,都会执行指定的回调函数。 这为在Promise是否成功完成后都需要执行的代码提供了一种方式。避免了同样的语句需要在then()catch()中各写一次的情况。

具体用法:

// 不使用 finallynew Promise((resolve) => {   resolve()}).then(() => {  console.log('success')  console.log('finally')}).catch(() => {  console.log('error')  console.log('finally')})

// 使用 finallynew Promise((resolve) => {   resolve()}).then(() => {  console.log('success')}).catch(() => {  console.log('error')}).finally(() => {    console.log('finally')})

接口定义:

interface Promise {/**     * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The     * resolved value cannot be modified from the callback.     * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).     * @returns A Promise for the completion of the callback.     */finally(onfinally?: (() => void) | undefined | null): Promise<T>}

代码实现:

class MyPromise {  // 无论如何都会执行  public finally(onfinally?: onFinally): MyPromise {return this.then((value) =>        MyPromise.resolve(// 如果 onfinally 返回的是一个 thenable 也会等返回的 thenable 状态改变才会进行后续的 Promisetypeof onfinally === 'function' ? onfinally() : onfinally        ).then(() => value),(reason) =>        MyPromise.resolve(typeof onfinally === 'function' ? onfinally() : onfinally        ).then(() => {throw reason        })    )  }}

Promise.allSettled

ES2020提出

Promise.allSettled(iterable)方法接收一个iterable对象,返回一个 Promise 实例,该实例的状态总是fulfilledpending。在 iterable 参数内所有的 thenable 状态不论为fullfilled还是rejectedreslovereject的值都会被包装成一个对象保留,当所有的thenable执行完毕后该 Promise 实例会reslove一个包含了这些所有对象的数组。如果有thenable的状态为pending,此实例的状态也为pending

具体用法:

const promise1 = new Promise((resolve) => {  resolve(1)})

const promise2 = new Promise((resolve) => {  resolve(2)})const promise3 = new Promise((resolve, reject) => {  reject(3)})

Promise.allSettled([promise1, promise2, promise3]).then(console.log)/*打印结果为:[  { status: 'fulfilled', value: 1 },  { status: 'fulfilled', value: 2 },  { status: 'rejected', reason: 3 }]*/

接口定义:

interface PromiseConstructor {  /**     * Creates a Promise that is resolved with an array of results when all     * of the provided Promises resolve or reject.     * @param values An array of Promises.     * @returns A new Promise.     */    allSettledextends readonly unknown[] | readonly [unknown]>(values: T):Promisein keyof T]: PromiseSettledResultextends PromiseLike ? U : T[P]> }>;/**     * Creates a Promise that is resolved with an array of results when all     * of the provided Promises resolve or reject.     * @param values An array of Promises.     * @returns A new Promise.     */    allSettled(values: Iterable): Promiseextends PromiseLike ? U : T>[]>;}

代码实现:

class MyPromise {static allSettledextends readonly unknown[] | readonly [unknown]>(    values: T  ): MyPromise<    {      -readonly [P in keyof T]: PromiseSettledResult<        T[P] extends PromiseLike ? U : T[P]      >    }  >static allSettled(    values: Iterable  ): MyPromiseextends PromiseLike ? U : T>[]>// 重载函数的返回值有冲突,想不报错需要使用联合类型,这边图省事直接用 any 了static allSettled(values: Iterable): MyPromise<any> {// 大体写法参照 Promise.all()return new MyPromise((reslove) => {const resultArr: any[] = []const doneArr: boolean[] = []// 获取迭代器const iter = values[Symbol.iterator]()// 当前值let cur = iter.next()const resolveResult = (value: any, index: number, done?: boolean) => {        resultArr[index] = {          status: Status.FULFILLED,          value        }        doneArr[index] = trueif (done && doneArr.every((item) => item)) {          reslove(resultArr)        }      }for (let i = 0; !cur.done; i++) {const value = cur.value        doneArr.push(false)        cur = iter.next()if (isPromise(value)) {          value.then((value) => {              resolveResult(value, i, cur.done)            },(reason) => {// 这里和 resolve 基本也没什么区别,修改一下状态和属性就ok了              resultArr[i] = {                status: Status.REJECTED,                reason              }              doneArr[i] = trueif (cur.done && doneArr.every((item) => item)) {                reslove(resultArr)              }            }          )// 不是 thenable 直接存储        } else {          resolveResult(value, i, cur.done)        }      }    })  }}

Promise.any

ESNEXT提出,还处于实验版本,只有少部分浏览器支持

Promise.any(iterable) 方法接收一个 iterable 对象,返回一个 Promise 实例。只要iterable中的一个 thenable 的状态为fulfilled,就返回那个thenablereslove的值,并且该实例的状态也为fulfilled。如果iterable中全为thenable并且状态全部为rejected,该实例的状态也为rejected并且reject一个AggregateError类型的实例(它是 Error 的一个子类,用于把单一的错误集合在一起,目前还在实验阶段,只有少部分浏览器支持)。本质上,这个方法和Promise.all()是相反的。

具体用法:

const pErr = new Promise((resolve, reject) => {  reject("总是失败");});

const pSlow = new Promise((resolve, reject) => {  setTimeout(resolve, 500, "最终完成");});

const pFast = new Promise((resolve, reject) => {  setTimeout(resolve, 100, "很快完成");});

Promise.any([pErr, pSlow, pFast]).then((value) => {  console.log(value);  // pFast fulfils first})// 打印结果为: "很快完成"

接口定义:

interface PromiseConstructor {   /**     * The any function returns a promise that is fulfilled by the first given promise to be fulfilled, or rejected with an AggregateError containing an array of rejection reasons if all of the given promises are rejected. It resolves all elements of the passed iterable to promises as it runs this algorithm.     * @param values An array or iterable of Promises.     * @returns A new Promise.     */  any(values: (T | PromiseLike)[] | Iterable>): Promise}

代码实现:

class MyPromise {static any(    values: (T | PromiseLike)[] | Iterable>  ): MyPromise {return new MyPromise((resolve, reject) => {// 接收迭代器const iter = values[Symbol.iterator]()let cur = iter.next()const doneArr: boolean[] = []for (let i = 0; !cur.done; i++) {const value = cur.value        cur = iter.next()        doneArr.push(false)if (isPromise(value)) {// 如果为 thenable,根据该 thenable 的状态进行判断          value.then(resolve, () => {            doneArr[i] = true// 只有传入迭代器的值全是 thenable 并且 thenable 的状态全部为 rejected 才会触发if (cur.done && doneArr.every((item) => item)) {// 应该抛出 AggregateError 的错误类型,但是因为 AggregateError 因为是实验版本,所有只有最新版浏览器才会有,我这里就用 Error 代替了const e = new Error('All promises were rejected')              e.stack = ''              reject(e)            }          })        } else {          resolve(value)        }      }    })  }}

总结

本文使用 typescript,根据类型定义从零开始实现了一个 Promise,其中重点深入了对于then方法的处理。虽不完美,但也算达到了想要的效果。作者技术有限,如果有什么错误或遗漏的地方还请在评论区中指出,顺便求个?。

文章的代码已上传至github:

https://github.com/Col0ring/learning-es6/tree/main/Promise

- EOF -

推荐阅读  点击标题可跳转

1、「一次写过瘾」手写 Promise 全家桶 + Generator + async/await

2、这次彻底搞懂 Promise(手写源码多注释篇)

3、手写 Promise 全家桶 + Generator + async/await

觉得本文对你有帮助?请分享给更多人

推荐关注「前端大全」,提升前端技能

点赞和在看就是最大的支持❤️

方法 手写promise_实现一个符合 Promise/A+规范的 Promise(typescript 版)相关推荐

  1. all方法 手写promise_我团队的一年前端实现Promise所有方法

    从零手写Promise完整版 随着前端技术的不断发展,用户对界面的要求也在不断提高,现在的前端不再是之前的html+css, 而是html+css+js,但是想学好js首先要知道js的核心在于异步,说 ...

  2. all方法 手写promise_试题-手写实现一个 promise.all - 拿OFFER

    Promise.all的实现和原理 Promise.all 功能: Promise.all(iterable)返回一个新的Promise实例,此实例在iterable参数内素有的Promise都ful ...

  3. 方法 手写promise_手写Promise类

    手写简单promise 1.分析promise核心逻辑 先看一段代码 let p = new Promise(); //说明Promise是一个类(class),需要new实例化 let p = ne ...

  4. 方法 手写promise_高级前端养成37js专精06之手写promise(上)

    面试答题万金油方法论顺序(高级前端面试套路) 该技术要解决什么问题-why 该技术是怎么解决它的-how 该技术有什么优点(对比其他技术) - pros 该技术有什么缺点-cons 如何解决这些缺点- ...

  5. all方法 手写promise_实现Promise.allSettled

    离上次写了 Promise.all 实现之后,已经隔了,呃,快一年了... 为什么又想起来写 Promise 的其他静态方法的实现呢?原因是最近已经连续两次收到了某跳动公司的面试邀请了.想着虽然不一定 ...

  6. all方法 手写promise_前端进阶高薪必看手写源码篇

    前言 此系列作为笔者之前发过的前端高频面试整理的补充 会比较偏向中高前端面试问题 当然大家都是从新手一路走过来的 感兴趣的朋友们都可以看哈 初衷 我相信不少同学面试的时候最怕的一个环节就是手写代码 大 ...

  7. 手写一款符合Promise/A+规范的Promise

    手写一款符合Promise/A+规范的Promise 长篇预警!有点长,可以选择性观看.如果对Promise源码不是很清楚,还是推荐从头看,相信你认真从头看到尾,并且去实际操作了,肯定会有收获的.主要 ...

  8. 深入理解Promise并写一个符合Promise a+规范的Promise代码

    深入理解Promise并写一个符合Promise a+规范的Promise代码 关于Promise函数可以参考我写的这篇文章https://www.cnblogs.com/qiaohong/p/770 ...

  9. 实现一个完美符合Promise/A+规范的Promise

    原文在我的博客中:原文地址 如果文章对您有帮助,您的star是对我最好的鼓励- 简要介绍:Promise允许我们通过链式调用的方式来解决"回调地狱"的问题,特别是在异步过程中,通过 ...

最新文章

  1. 私钥分割 — Shamir Secret Sharing
  2. 从Web借鉴UI设计
  3. linux启动redis_Redis简介
  4. Spring中DispacherServlet与WebApplicationContext、ServletContext的关系和工作机制
  5. [hackerrank]Manasa and Stones
  6. 量词逻辑量词里面的v表示?_知识表示能力问答中的人工智能量词(MCQ)
  7. Shiro过滤器配置(ShiroFilterFactoryBean)
  8. 根据href给当前导航添加样式
  9. HDU1231 最大连续子序列【最大子段和】
  10. Django 高级(其他知识点)
  11. 常见视频高速接口分析MIPI,LVDS,RGB,HDMI
  12. php如何用sql语句修改数据库,SQL语句进行数据表的增删改查教程(phpMyAdmin使用教程)...
  13. 繁簡替互換(SQL)
  14. 除了Google Adsense外比较适合英文站投放的国外广告联盟
  15. Go语言开发Windows应用
  16. 爬虫增加代理池:使用稳定第三方芝麻代理IP 教程(详细可用)
  17. 2019年,IT行业发展前景
  18. 25 Creative Ways to Promote Your App for Free
  19. 雷凌linux车机升级_丰田雷凌原车高德地图升级方法(附说明)
  20. 地下城与勇士(DNF)安特贝鲁峡谷副本(根特外围、根特东门、根特南门、根特北门、根特防御战、夜间袭击战、补给线阻断战、追击歼灭战、决战哈尔特山)(童年的回忆)

热门文章

  1. HDU2563 统计问题【递推+打表】
  2. B00011 unordered_map
  3. matlab 实用程序片段
  4. C++基础::拾遗琐碎
  5. python怎么读write_python中write方法是如何使用?
  6. python软件下载3版本-【python3下载】python3下载安装 中文版-七喜软件园
  7. python编程入门课 视频-Python入门到精通视频教程下载[21课程全]
  8. python能做什么游戏-Python有做大型游戏的潜力吗?
  9. 语音识别(ASR)基础介绍第四篇——当今流行做法与CTC-阿里云开发者社区
  10. 数字语音识别 - 源码下载|数值算法/人工智能|matlab例程|源代码 - 源码中国