从 Promises/A+ 看异步流控制

同步发表于 个人博客html

单线程的挑战

众所周知,JavaScript 是一种 单线程 编程语言。单线程注定了在当前执行上下文栈中的代码尚未执行完成时,后续全部 task/micro-task queue 中的 task/micro-task 代码都不会执行。前端

为何要单线程,多线程很差吗?git

JavaScript 设计之初,它的设计定位是一种主要用于操做 DOM 的图灵完备语言。为了不并发操做 DOM 带来的并发操做问题,并结合其语言定位,就注定了它单线程的命运。github

Ajax 出现以前,全部的 JS 代码都是以同步的方式逐步执行。Ajax 的出现为网页带来了在不刷新页面的前提下就能更新页面的能力。Web 世界在搭上了 Ajax 的快车以后飞速发展。可是,当网络条件不稳定时问题出现了,同步的 Ajax 会始终占用主线程的执行上下文,致使在请求没有响应的期间,全部后续的同步代码都没法执行,那么整个网页就陷入了 “瘫痪”,全部的用户交互都被堵在了 task queue 中。web

用户交互属于 task 而非 micro-task。编程

这个时候聪明的人们发明了 异步请求。在每次请求发出后,设置一个回调函数等待响应返回再处理响应,而且在等待的过程当中 再也不占用当前执行上下文设计模式

然而事情并无那么简单。随着时间推移,网页需求愈来愈复杂,网页应用也愈来愈复杂,一不当心咱们就掉进了 callback hell,究其缘由是咱们没有一个优雅的前端异步流控制 asynchronous flow control 解决方案。api

以下:promise

const verifyUser = function(username, password, callback) {
  effect.verifyUser(username, password, (error, userInfo) => {
    if (error) {
      callback(error)
    } else {
      effect.getRoles(username, (error, roles) => {
        if (error) {
          callback(error)
        } else {
          effect.logAccess(username, error => {
            if (error) {
              callback(error)
            } else {
              callback(null, userInfo, roles)
            }
          })
        }
      })
    }
  })
}
复制代码

常规的 callback API 再也不可以知足包含大量异步操做的网页应用,大量的 异步操做 就意味着必须有一个合理的 异步流控制 方案来掌控各类异步流操做。浏览器

那么后来咱们是如何避免回调地狱来清晰地实现异步流控制?

答案是 Promises/A+ 规范。

本文将严格依据 Promises/A+ 规范约束最终实现一个自定义版本且经过 规范测试套件 的异步流控制解决方案。在 @lbwa/promise-then 你能够找到完整的 Promises/A+ 实现。

什么是 Promises/A+

Promises/A+ 一个略微中二的名字,但它给社区给开发者带来了异步操做流的重磅炸弹—— Promise。 规范定义了一个名为 Promise 的对象来承载当下异步操做流的 最终结果。它用同步的方式表示了当下异步操做流的状态以及与状态相关联的返回值。

  1. 在操做成功的状况下,异步操做的返回值 value 是多少;

  2. 在操做失败的状况下,又是由于什么缘由 reason 失败的。

// 仅借助 ts 的接口来表示 Promise 的 **抽象** 结构,不表明实现细节
interface Promise {
  state: PromiseState
  value: any // 不管是成功仍是失败,都经过该字段存储对应的值或失败缘由
}
复制代码

Promise 对象交互的 首要方式 都是经过一个名为 then 的方法来实现,如修改 Promise 的状态,返回异步操做返回值或异步操做失败缘由。

那么在定义了这样的规范核心的前提下,全部的规范内容都是围绕实现以上两点核心功能点。

如何表示异步流的当下状态

根据上文中 Promises/A+ 规范对 Promise 对象的定义。从本质上来说,Promise 是一种抽象的状态模型,一种 有限状态机。规范在第一章首要位置首先定义了 3 个值来表示 Promise 对象的状态,即表示了全部异步操做流可能出现的三种状态:

  1. pending 状态,如字面意思同样,表示当下异步操做流正在执行中,正在 等待 异步流操做的结果。

  2. fulfill 状态,表示当下异步操做流已经操做 成功,并在 Promise 对象中包含了当下操做的执行结果。

    本文遵循 Promise/A+ 的表示方式,fulfill 等价于 ES6 Promise 中的 resolve 状态。

    interface FulfilledPromise {
      state: States.fulfilled
      value: any
    }
    复制代码
  3. reject 状态,表示当下异步操做流操做 失败,并在 Promise 对象中包含了当前操做失败的缘由 reason

    interface RejectedPromise {
      state: States.rejected
      value: Error
    }
    复制代码

全部的异步操做流仅存在以上三种操做状态,那么笔者借助 TypeScript 中的静态枚举可实现如下结构包含全部的静态状态变量值:

const enum States {
  pending = 'pending',
  fulfilled = 'fulfilled',
  rejected = 'rejected'
}
复制代码

在笔者我的对 Promise/A+ 的实现中,之因此使用静态枚举的缘由是在 TypeScript 中全部的静态枚举值均可在 compile time 时期被直接编译为 静态变量字面量,而不是 JS 运行时中的一个朴素的 JS 对象,这里的静态枚举起到的做用相似于一些编译型语言(如 C++)中的 编译时常量

实现的技术核心是什么

不管是回调地狱仍是 Promises/A+ 规范,首先抛开实现技术细节上来看,全部的异步操做流程都具备:

  1. 注册回调

    由于是异步流控制,异步流内部相对于外部模块来讲始终是在进行异步操做,那么在执行异步操做开始,进行中,结束时的任意阶段都首先经过异步操做模块对外暴露接口 注册一个或多个回调函数。

    回调地狱只能在异步流开始以前进行回调注册,而 Promises/A+ 定义了同一个 Promise 实例能够调用屡次 then 函数,即实现了多阶段多个回调注册。

  2. 触发回调

    在异步流再也不为 pending 状态时,那么给对应状态注册的全部回调函数,会 依据注册顺序 分别获得执行。

经过对关键点的梳理,全部的回调函数都是直接依赖于异步流的状态的,那么它们都是当前异步流状态变化的 观察者,而当前异步流的状态变化始终是全部回调函数的 主题。结合两者的关系分析,不难经过 observer 模式实现异步流控制的 技术核心

这里笔者的实现是将 then 回调传入的回调函数 onFulFilledonRejected 合二为一看待,它们造成一个 回调函数 总体 ThenableCallbacks。该集合总体 直接依赖 于当前的 Promise 实例,当前实例做为全部回调集合主题 subject。侧重于集合总体观察 Promise 状态变换,而非具体状态值。在存在 Promise 状态值变换,即 subject 变化时,将广播至每个回调集合。故说是经过 observer 模式(而不是 publish/subscribe 模式)。

interface ThenableCallbacks {
  onFulfilled?: OnfulfilledCallback
  onRejected?: OnRejectedCallback
}
复制代码
ThenableCallbacks --观察--> Promise.state 的变化
复制代码

固然存在另一种抽象思路是,将 onFulfilledonRejected 都认为是独立的个体,它们分别经过 fulfilledQueuerejectedQueue间接依赖 当前的 Promise 实例。而且 fulfilledQueuerejectedQueue 经过 message brokerevent bus 来订阅各自的指望主题 topic。这个时候整个模式侧重的再也不是 Promise 实例的状态变化,而是实例的某一个具体的状态值。全部的回调函数的触发都是经过 message brokerevent bus 来发布对应的 topic 来实现函数调用。而这种抽象设计模式正是 publish/subscribe 模式。

type FulFilledQueue = OnfulfilledCallback[]
type RejectedQueue = OnRejectedCallback[]
复制代码
onFulfilled --注册--> fulfilledQueue --订阅--> fulfilled state
onRejected --注册--> rejectedQueue --订阅--> rejected state
复制代码

这里两者设计模式实现的异步流控制方案复杂程度并无太大差别,本文也将采用第一种 observer 方式来严格实现技术细节。

异步控制流的初始化

在初始化异步流阶段,咱们应该如何实现 Promise 初始化?经过对规范中 The promise resolution procedure 阅读可见,规范中将异步流初始化定义为一种基于单个 promise 和一个单值 x 处理的异步流抽象操做,记为 [[Resolve]](promise, x)

  1. 这里 promise 就是在初始化 Promise 构造函数所返回的 Promise 实例,记为 $0。它始终是对外暴露的。

  2. 单值 x 是做为当前异步流的结果载体,它自己是表示一个异步流的结果返回值,不该被模块外部直接修改。当 x 表示一个非 Promise 实例的值时,它的存在会直接被当前 $0 实例直接引用为 Promise 的实例 value 字段的值。

    细心的读者可能发现,x 值是能够为另外一个 Promise 实例的,那么在这种状况下,当前 $0 实例会尝试直接同步 x 变量所引用的 Promise 实例的状态 state,并将该实例的 value 字段的异步流结果值,直接赋值给 $0 实例的 value 字段。

promise procedure

在基于以上两点的前提下,咱们在初始化 Promise 阶段的核心目标是,实现一个有限状态机,并接收一个 executor接收 状态机对外的暴露的状态变换器。该变换器的核心目标是,在被调用的状况下,修改状态机内部的状态值。另外,基于 Promises/A+ 规范中的定义,全部已经固定的状态,没法被再次修改

Promise 实例的状态被状态变换器触发改变的条件下,同时经过状态变换器来接收执行结果或执行错误。并将执行结果或执行错误赋值给 Promisevalue 字段,以供 Promise 的交互接口 then 函数来获取对应的异步流操做状态和操做结果。

class Promise {
  // 初始化实例的状态为 pending 状态
  private state: States = States.pending
  private value: any = undefined

  // 模块内部的状态变换器核心
  private _settle(state: Exclude<States, States.pending>, result: any) {}

  constructor(executor: (onFulfilled, onRejected) => void) {
    executor(
      function fulfill(result?: any) {}, // 对外状态变换器
      function reject(reason?: Error) {} // 对外状态变换器
    )
  }
}
复制代码

在上文中,经过简单代码展现了 Promise 初始化的核心抽象流程。fulfill 函数对应了前文所述的向模块外部暴露,用于修改 Promise 状态机的 fulfilled 状态的状态变换器;而 reject 函数一样是向外暴露,且用于修改 Promiserejected 状态的状态变换器。

另外,咱们抛开技术实现细节,从函数的抽象功能来看。不管是 fulfill 函数仍是 reject 函数它们起到的做用不外乎:

  1. 变换当前 Promise 实例状态机的状态至一个指定值。

  2. 被调用时,将接收外部传递的异步流操做的结果返回值,并将其在当前 Promise 实例中保存。

  3. 在实现了 Promise 状态机状态变换后,应该发送 异步通知 给全部的以前已经注册的观察当前状态的回调函数。

基于以上三点和 Don't repeat yourself 原则,那么咱们能够从功能抽象的角度来实现一个 _settle 内部函数,用于实现 真正的状态变换 功能。

interface ThenableCallbacks {
  onFulfilled?: (result?: any) => any
  onRejected?: (reason?: )
}

class Promise {
  private _observers: ThenableCallbacks[] = []
  // ...
  private _settle(state: Exclude<States, States.pending>, result: any) {
    if (state !== States.pending) return
    this.state = state
    this.value = result
    this._notify(state === States.fulfilled ? 'onFulfilled' : 'onRejected')
  }

  private _notify(type, message?: any) {}
  // ...
}
复制代码

那么在通知状态的时,为何要实现 异步通知 而不是直接经过 同步通知 观察状态变化的回调函数?

这是由于在 Promise/A+ 中已经明肯定义,全部 onFulfilledonRejected 回调函数,不能在当前执行上下文栈中执行,而是必须等到 一个全新的仅有平台代码的执行上下文栈 中执行。

平台代码是指仅有 JS 引擎,环境和 Promise 实现代码而不含其余业务代码的场景。

如何实现回调函数调用

根据 ECMA 262 规范 8.3 Execution Contexts 章节模型,全部平台 JS 代码执行都是依赖于 执行上下文 execution context,其容器为 执行上下文栈 execution context stack,而 执行上下文栈 是由 task queuemicro-task queue 来驱动。

在代码执行完成时,即当前 执行上下文 移交 running execution context 的标志时,会退出当前 执行上下文栈。在当前 running task/micro-task 执行完成之际,也就是当前 执行上下文栈 中清空后,会由下一个 task queue 中的 taskmicro-task queue 中的 micro-task 来建立一个 执行上下文栈 栈底的执行上下文,并继续执行新的 task/micro-task 中的代码。

在 JS 中执行上下文的类型分为:函数执行上下文,全局执行上下文,eval 执行上下文(如无必须,不推荐使用,故不讨论)。全部的函数执行都会建立一个新的执行上下文用于追踪代码执行,反之,其余代码执行都会在全局执行上下文中执行。

那么依据上文,回调函数的执行必须等到当前执行上下文栈清空,并在一个空白的执行上下文栈来执行 onFulfilledonRejected 回调,即除开平台代码外,onFulfilledonRejected 回调函数必须是当前执行上下文栈中最靠近栈底的第一个执行上下文。

根据 JavaScript事件驱动模型

  1. 若须要将回调函数加入到 task queuequeue task,那么咱们须要基于一个 task 引导 来执行待执行的回调函数,如 setTimeoutsetImmediate(限 IENode.js 环境);
  1. 若须要将回调函数加入到 micro-task queuequeue microtask,那么咱们须要基于一个 micro-task 引导 将待执行回调函数加入到 micro-task queue 中,如 MutationObserver(限浏览器环境)、process.nextTick(限 Node.js 环境)。

本文也将依据以上理论,基于 setTimeout 来实现名为 marcoTaskRunnertask 建立函数。

function marcoTaskRunner(this: any, fn: Function, ...args: any[]) {
  return setTimeout(() => fn.apply(this, args), 0)
}
复制代码

那么完善在新的执行上下文中执行 onFulfilledonRejected 回调函数的基础后,不难写出向全部回调函数观察者广播的 _notify 函数,并基于此获得整个状态修改后的通知观察者流程:

settle promise

class Promise {
  private _observers: ThenableCallbacks[] = []

  // ...
  private _settle(state: Exclude<States, States.pending>, result: any) {
    if (state !== States.pending) return
    this.state = state
    this.value = result
    this._notify(state === States.fulfilled ? 'onFulfilled' : 'onRejected')
  }

  private _notify(type, message?: any) {
    this._observers.forEach(callbacks => {
      const handler = callbacks[type]
      if (isFunction(handler)) {
        // spec 2.2.5
        // https://promisesaplus.com/#point-35
        handler.call(null, message)
      }
    })
  }
  // ...
}
复制代码

当在前文中 executor 的外部状态转换器 fulfillreject 函数触发 Promise 内部状态变换器后,将触发状态修改,结果赋值,调用回调函数一系列流程。这便是实现有序的异步流控制流程 Promise 方案的 技术核心 之一。

剩余一些在初始化 Promise 阶段的特定流程可在 the promise resolution procedure 找到。本文将省略已经在规范中明肯定义的普通初始化流程。

then 方法实现

Promise/A+ 中,定义了全部异步流控制的回调函数都是经过 then 函数来注册。then 函数始终返回一个 新的 Promise 实例。

2.2.7 then must return a promise.

深刻思考一下,为何 then 要返回一个新的 Promise 实例。这样作的缘由是,then 函数返回的 Promise 实例可继续被下一个 then 函数访问,这样循环往复,最终可实现 Promise 链式(级联) 调用。

asyncAction()
  .then(function callbackA() {}, function catchA() {})
  .then(function callbackB() {}, function catchB() {})
  .then(function callbackC() {}, function catchC() {})
  .then(function callbackD() {}, function catchD() {})
复制代码

为何要支持 级联调用

级联调用 私觉得最大的 优点 在于开发者能够将不一样的异步流将一个繁琐的异步流操做 分解 decouple 为更小的异步流操做,而更小的操做意味着更多的操做组合可能性。这样的优点可大大加强代码灵活性,可读性,维护性。全部的异步流操做再也不受限于传入的 API 格式,若是有必要任意组合多个异步流操做。

在不一样时期处理回调函数

class Promise<T> {
  // ...
  then(onFulfilled?: OnFulfilled<T>, onRejected?: OnRejected): Promise<T> {
    return new Promise<T>((resolve, reject) => { if (isStrictEql(this.state, States.pending)) { // TODO return } if (isStrictEql(this.state, States.fulfilled)) { // TODO return } if (isStrictEql(this.state, States.rejected)) { // TODO return } }) } } 复制代码

Promise 显式原型上的 then 方法将根据 Promise 状态机可能的三种状态进行三种不一样类型的回调函数操做。

结合 Promises/A+2.2 The then method 中的定义:

  1. 不管是 onFulfilled 仍是 onRejected 回调函数,必须等待 this 所指向的 Promise 实例的状态固定为 fulfilledrejected 以后才能被调用。

    换句话说,当 this 所指的 Promise 实例为 pending 状态时,应该将 onFulfilledonRejected 设置为当前 Promise 状态的观察者,经过 observer 模式实现状态修改广播。

    固然这里也更加细致地分别订阅 fulfilled 状态和 onRejected 状态,进而基于 publish/subscribe 模式实现发布状态的 topic,进而触发调用已经注册的回调函数队列。

  1. this 所引用的实例状态为 fulfilled 时,应该调用全部以前注册的 onFulfilled 函数,并将 this 引用的实例的 value 字段做为 onFulfilled 的参数传入,以用来表示 Promise 实例所表明的异步流的操做结果。
  1. this 所引用的实例为 rejected 状态时,应该调用 onRejected 函数,并将 this 引用的实例的 value 值,做为 onRejected 的参数传入,以用来表示 Promise 实例被 rejected 的缘由。

那么在基于以上对规范的抽象总结后,不可贵到:

class Promise<T> {
  // ...
  private _register(onFulfilled: OnFulfilled<T>, onRejected: OnRejected) {
    // 方案一:observer 模式,以回调集合为基准,做为 Promise 实例变化的观察者
    this._observers.push({
      onFulfilled,
      onRejected
    })

    // 方案二:publish/subscribe 模式,以单个回调为基准,订阅特定的 topic
    // this._onFulfilledSubs.push(onFulfilled)
    // this._onRejectedSubs.push(onRejected)
  }

  // ...
  then(onFulfilled?: OnFulfilled<T>, onRejected?: OnRejected): Promise<T> {
    return new Promise<T>((resolve, reject) => { if (isStrictEql(this.state, States.pending)) { this._register( result => onFulfilled(result), reason => onRejected(reason) ) return } if (isStrictEql(this.state, States.fulfilled)) { return marcoTaskRunner(() => onFulfilled(this.value)) } if (isStrictEql(this.state, States.rejected)) { return marcoTaskRunner(() => onRejected(this.value)) } }) } } 复制代码

那么咱们是否是就最终实现了 Promise 显式原型上的 then 方法了呢?

并无,由于在规范中,还定义了 then 方法的两个参数都是 可选参数,而以上咱们对 then 方法的实现都是基于 onFulFilledonRejected 都是强制参数,且都为函数的状况。

class Promise<T> {
  // ...
  then(onFulfilled?: OnFulfilled<T>, onRejected?: OnRejected): Promise<T> {
    const createFulfilledHandler = (
      resolve: OnFulfilled<T>,
      reject: OnRejected
    ) => (result?: any) => {
      try {
        if (isFunction(onFulFilled)) {
          return resolve(onFulFilled(result))
        }
        return resolve(result)
      } catch (evaluationError) {
        reject(evaluationError)
      }
    }

    const createRejectedHandler = (
      resolve: OnFulfilled<T>,
      reject: OnRejected
    ) => (reason?: Error) => {
      try {
        if (isFunction(onRejected)) {
          return resolve(onRejected(reason))
        }
        return reject(reason)
      } catch (evaluationError) {
        reject(evaluationError)
      }
    }

    return new Promise<T>((resolve, reject) => { const handleFulfilled = createFulfilledHandler(resolve, reject) const handleRejected = createRejectedHandler(resolve, reject) if (isStrictEql(this.state, States.pending)) { this._register( result => handleFulfilled(result), reason => handleRejected(reason) ) return } if (isStrictEql(this.state, States.fulfilled)) { return marcoTaskRunner(() => handleFulfilled(this.value)) } if (isStrictEql(this.state, States.rejected)) { return marcoTaskRunner(() => handleRejected(this.value)) } }) } } 复制代码

基于规范 then 函数的功能定义,将 fulfilledrejected 状态分别对应的功能逻辑抽象出两个高阶函数 createFulfilledHandlercreateRejectedHandler。由两个高阶函数分别生成对应状态的处理函数。

至此,以上便是 then 函数中的核心功能点:

  1. 依托前文已经实现的 Promise 实例化流程,then 函数不论在何种状况下都始终返回一个新的 Promise 实例。

  2. then 函数内部会对 this 所引用的 Promise 实例的状态进行针对性的回调函数处理:

    1. thispending 状态的 Promise 实例,那么在当前 event loop 中不会调用任何回调函数,而且,将全部回调函数与 Promise 造成绑定关系,结合前文中的状态变换器,在发生任意的状态固定时,将触发对应的回调函数调用。

    2. thisfulfilled/rejected 状态的 Promise 实例,即代表该 Promise 实例状态已经固定,那么指定状态的回调函数将在下一轮 event loop 中被调用。

结论

以上笔者避免繁琐地把 Promises/A+ 规范的一条条定义逐步进行阐述,而是着重在于阐述分析规范中的 核心思惟模式技术细节关键点。笔者私觉得技术实现方案千差万别,实现的技术细节并非最有价值的东西,最有价值实际上是技术背后的思想。对于异步流控制解决方案来讲,对于 Promises/A+ 来讲,最核心的思惟模式是:

  1. 经过一个有限状态机表示一个异步流控制流程。

  2. 经过状态机内部状态变化,触发状态修改,进而触发回调队列的顺序调用。本质上是一种基于 observerpublish/subscribe 模式,回调函数与异步流状态之间的相互依赖的关系。

  3. 规定 then 函数如 Promise 构造函数同样始终返回一个新的 Promise 实例,使得 链式(级联) 调用成为了可能。

横向对比

在深刻 Promises/A+ 规范以后,如雨后春笋般出现了各类具备不一样适用场景的规范实现,应用最普遍的便是众所周知的 ECMA 262 规范中的 JS 内置对象 —— Promise。那么 Promise 内置对象以及一样具备异步流控制能力的 async function 又是如何为开发者提供高效简洁的异步流控制?

ECMA 262 实现

根据最新的 ECMA 262 中对 Promise 章节的阐述,Promises/A+ 规范中 fulfilled 状态在 ECMA 262 中被定义为 resolved 状态。Promise 内置对象不只仅实现了 Promises/A+ 标准,还在其基础上进行了拓展,Promise 显式原型对象上不只仅包含 then 函数,还包含了:

功能函数 使用场景
catch 捕获 Promise 链中以前未被处理的 rejected 状态。等价于 promise.then(null, function catch() {})
finally 只要 Promise 链以前的 Promise 状态获得固定(即不为 pending 状态),都始终触发传入 finally 的回调函数。
all 适用于多个异步流并发的场景,当存在一个包含多个 Promise 实例的列表时,只有当全部的 Promise 实例状态都为 resolved 状态时,all 函数返回的 Promise 实例的状态才会为 resolved
race 适用于多个异步流并发的场景,当存在一个包含多个 Promise 实例的列表时,第一个固定状态的 Promise 状态和值,会被赋值给当前 Promise.all 生成的 Promise 实例。
allSettled 适用于多个异步流并发的场景,当存在一个包含多个 Promise 实例的列表时,只有当全部 Promise 实例都固定状态时才会返回一个结果列表,该列表展现了对应索引 Promise 的状态和结果值(或 rejected 缘由)。

本质上 JS 中的 Promise 内置对象是 Promises/A+ 的实现的拓展版本,在完成了规范定义的要素和功能以后,又在其核心功能上拓展出了以上几个便捷的 API 用于处理异步流控制。

async function 又是什么

async function 在实际应用中表现为 async ... await 表达式,它的出现一样是为了给予开发者异步流控制的能力。

async function 的出现是为了什么?如前文阐述,Promise 支持级联调用,那么可能会出现一种状况——过长的级联调用链。而 async function 给开发者提供了一种 以同步的方式 来书写异步流程的可能。

  • 级联调用链
asyncAction()
  .then(function callbackA() {}, function catchA() {})
  .then(function callbackB() {}, function catchB() {})
  .then(function callbackC() {}, function catchC() {})
  .then(function callbackD() {}, function catchD() {})
复制代码
  • 以同步的方式书写异步流程
async function newAsyncActionChain() {
  try {
    const resultA = await callbackA()
  } catch (error) {
    catchA(error)
  }

  try {
    const resultB = await callbackB()
  } catch (error) {
    catchB(error)
  }
  // ...
}
复制代码

这样的同步方式更加符合人类的思惟定势。可是 async function 一样具备缺陷,每一次经过 async await 表达式获取异步操做结果都须要使用 try...catch 语句来捕获全部异步流的 rejected 状态。

对比 async functionPromise 这两种异步流控制解决方案,它们各有优点,又各有弊端:

异步流控制 相对优点 相对劣势
async function 以同步思惟写异步 须要使用额外代码 try...catch 处理异常
Promise 级联调用,可拆分更加细致的异步流控制 可能出现过长的级联调用链

另外,若是读者在使用 TypeScript 且设置较低版本的编译目标的后, TypeScript 会将代码中的 async function 编译为基于 Promise 函数和 Generator 函数的结合体。 而其余一些流行的 async function 方案,如 @babel/plugin-transform-async-to-generator 一样会将 async function 转换为 Generator 函数。

不管是从哪一个实现上来看,async function 的流行 polyfill 都离不开 Generator 函数,由于 Generator 函数天生具备 暂停 函数执行的能力。这一点正好符合 await 语句的功能点。async function 本质上能够认为是一个具备自动执行 next() 功能的 Generator 函数。

结论

经过以上对比发现,不管是 Promise 内置对象所提供的级联调用,仍是 async function 的以同步方式书写异步流程,它们的出现都是为了更好的解决开发时各类异步流控制的问题。

结合全文来看,不管是经过什么样的方式实现异步流控制,其本质上,都是回调函数与异步流状态必须造成一种对应关系,在这种关系下,全部的回调函数都依赖于异步流状态的值的改变。在异步流状态改变的状况下,若存在对应的回调函数,就会被调用。

相关文章
相关标签/搜索