Promise拆解计划:由规范深入,从原理浅出

大家好,欢迎来到前端研习圈的今日分享。上期跟大家浅析了为什么要了解 Promises/A+ 规范

今天我们就来拆解一下 Promises/A+,从规范入手,重塑对 Promise 的理解, 接下来直接进入正题~

什么是 Promise

首先,我们看一下规范中如何定义 Promise

可以看到,规范中对 Promise 的描述是一个异步操作的最终结果。我们可以通过 then 方法注册回调来接收它的最终的 value 或者失败的 reason

这也是为什么在很多有关 Promise 的介绍中把它称为 状态机 的原因。由此可见,规范也并非处处都是晦涩难懂的黑话,定义还是非常简洁明了的。

开放的标准

同时 A+ 规范还是很开放的一套标准,在前言中,有这样一段描述

规范并没有约束 创建 一个 Promise 的方式,也没有限制将一个 Promise 的状态修改为 成功 或者 失败 的方式,而是选择聚焦于提供一个可互用的 then 方法。

如何理解这个 可互用 呢,其实也很简单,假设有两个基于 Promises/A+ 实现 AB,针对 A 创建的 promise 的 then 处理逻辑,放在 B 上也同样适用,这也就意味着,这套规范会为 then 增加很多约束条件,以实现 可互用

实际上也确实如此,A+ 中大部分的条例其实都是在规定 then 方法应该如何实现

约定俗成的术语

在进入 A+ 的为 Promise 制定的需求列表之前,我们先了解一下其中的 术语

关于 promisethenable 的描述大家可能会有点疑惑,为什么都是一个具备 then 方法的对象(函数本质上也是对象),却要叫两个名字? 其实很好解释,promise 是官方实现的叫法,而 thenable 是非官方实现的统称(社区实现的叫法)

value, reason, exception 规范中说得很明确,这里就不做多余的阐述了

状态的处理细则

规范中规定了一个 promise 的状态必须为 pendingfulfilled,rejected 三者之一

处在 pending 时, 可以变为 fulfilled,rejected 两者之一

变为 fulfilled 时,状态不能再改变,同时必须产出 value,且 value 也不能被改变

变为 rejected 时,状态不能再改变,同时必须产出 reason,且 reason 也不能被改变

说简单一点,promise 的状态只能被改变一次,要么成功要么失败, 有点不成功便成仁的味道了。

then 方法

关于 then 描述中, 规定了它需要接收两个参数

这里onFulfilledonRejected 也就是我们日常说的 成功的回调 和 失败的回调

约束条例

onFulfilledonRejected 不是函数的场景将直接被忽略


onFulfilled 是一个函数,它必须在 promise 变为 fulfilled 后调用,同时把 value 作为参数,且只能调用一次


onRejected 是一个函数,它必须在 promise 变为 rejected 后调用,同时把 reason 作为参数,且只能调用一次


onFulfilledonRejected 必须在平台代码的执行上下文结束之后调用。

这句话怎么理解呢,假设在 executor 中同步执行了 resolvereject,成功/失败的回调也必须等同步逻辑全部执行完后,才能执行。这也就是为什么 onFulfilledonRejected 会被放置在 micro task 中执行,而不是立即执行的原因

js 复制代码
const p = new Promise((resolve, reject) => {
  resolve(3)
  console.log(1)
})
console.log(2)
p.then((value) => {
  console.log(value)
})
// 输出 1 2 3

onFulfilledonRejected 必须作为普通函数调用,也就是说不考虑内部 this 的指向


then 在同一个 promise 上被调用多次,所注册的回调需要在 promise 变为 fulfilledrejected 后,按照注册的顺序执行


then 方法的返回值,必须是一个新的 promise(promise2),这也就是 promise 能够被 链式调用 的原因

onFulfilledonRejected 的返回值 x,需要交给一个名为 Promise Resolution Procedure 的流程去处理,主要是考虑 x 还是一个 promise 的场景,因此需要设计一个递归流程来处理,这个流程的条例在下文会讲

onFulfilledonRejected 如果执行报错,抛出异常 e,那么 新返回的promise2 的状态将变为 rejected

当 promise1 状态变为 fulfilledonFulfilled 不是一个函数时,promise2 将以相同的 value 变为 fulfilled

当 promise1 状态变为 rejectedonRejected 不是一个函数时,promise2 将以相同的 reason 变为 rejected

Promise Resolution Procedure

最后就是上文中提及的 Promise Resolution Procedure 流程,这是一个抽象出来的概念并非某个具体实现。它会接收一个两个参数,一个 promise 和一个 x

这里规范中提到,当 x 是一个 thenable 时, 会尝试让 promise 使用 x 最终的状态

看个例子吧

结合例子就不难发现,这样做的意义是保证 then 方法返回的 p2 的状态是由 p1onFulfilledonRejected 的返回值的状态决定的。

接下来我们来看整体流程的条例

promisex 的引用是同一个对象,则将 promise 置为 rejected 并抛出 TypeError 作为失败的 reason

也就是如下场景


x 是一个 promise,则会根据其状态做不同的处理

如果 x 处于 pending,那么 promise 需要等 x 成功或失败后再同步其状态;如果 x 已经成功或失败,那么 promise 则直接同步其状态


x 是一个普通对象或者函数时,这部分场景是 A+ 中最复杂的部分,考虑到单纯用文字描述可能会很抽象,这部分将放置在下期的 源码实现篇 中着重拆解

有能力的同学可以尝试直接原文


x 既不是一个普通对象也不是函数时,promisex 都将直接被置位 fulfilled


以上就是 Promises/A+ 规范中全部条例了

结尾

-> Promises/A+ 原文

经过对规范的拆解后,相信大家对 promise 的认识又有了不小的提升。不过还保留了一个大坑等着下期来补(条例2.3.3)。下期我们将从 规范 -> 实现 来切实的感受 promise 的工作流程

最后希望大家每天进步一点点,我们下期见~

相关推荐
江城开朗的豌豆28 分钟前
解锁Vue自定义指令:这些骚操作你可能还不知道!
前端·javascript·vue.js
江城开朗的豌豆38 分钟前
Vue和iframe的悄悄话:跨窗口通信小妙招
前端·javascript·vue.js
江城开朗的豌豆1 小时前
Vue的v-model到底是个啥?用了5年才知道还能这么玩!
前端·javascript·vue.js
饺子大魔王的男人9 小时前
【Three.js】机器人管线包模拟
javascript·机器人
希希不嘻嘻~傻希希10 小时前
CSS 字体与文本样式笔记
开发语言·前端·javascript·css·ecmascript
爷_11 小时前
Nest.js 最佳实践:异步上下文(Context)实现自动填充
前端·javascript·后端
爱上妖精的尾巴11 小时前
3-19 WPS JS宏调用工作表函数(JS 宏与工作表函数双剑合壁)学习笔记
服务器·前端·javascript·wps·js宏·jsa
—Qeyser11 小时前
让 Deepseek 写电器电费计算器(html版本)
前端·javascript·css·html·deepseek
喝拿铁写前端13 小时前
前端批量校验还能这么写?函数式校验器组合太香了!
前端·javascript·架构
然我14 小时前
面试官最爱的 “考试思维”:用闭包秒杀递归难题 🚀
前端·javascript·面试