【Promise 源码学习】第七篇 - 实现 Promise 返回值 x 的处理
一,前言
上篇,实现了 Promise 的链式调用功能,主要涉及到以下几个点:
介绍了 Promise 的链式调用,返回普通值和抛出异常的共 5 种情况;
分析了当前 Promise 源码的问题以及解决方案;
Promise 链式调用的实现、功能测试、执行过程分析;
Promise A+ 规范中提到了对 then 中方法返回值 x 的处理,核心方法:resolvePromise
;
本篇,将实现 Promise 对返回值 x 的处理;
二,Promise A+ 规范
上一篇,在实现了 Promsie 链式调用的过程中,介绍了返回值 x 为普通值和抛出异常的情况;
返回值 x 还有可能是 Promise 类型,根据 Promise 状态决定执行成功/失败处理;
下面是 Promise A+ 规范中,与 “Promise 解析过程”有关的内容;
1,规范
Promise A+ 规范相关内容:
2,翻译
Promise 的解析过程,是以一个 promise 和 一个值 做为参数的抽象过程,可以表示为[[Resolve]](promise, x).如果 x 是一个 thenable,它试图使 promise 采用 x 状态,假设 x 的行为至少有点像 promise.否则,使用 x 的值执行 promise.
这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。
运行 [[Resolve]](promise, x) 需遵循以下步骤:
2.3.1 如果 promise 和 x 指向同一个对象,使用 TypeError 作为据因拒绝执行 promise
2.3.2 如果 x 为 Promise ,则使 promise 接受 x 的状态:
2.3.2.1 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝;
2.3.2.2 如果 x 处于执行态,用相同的值执行 promise;
2.3.2.3 如果 x 处于拒绝态,用相同的据因拒绝 promise;
2.3.3 如果 x 为对象或者函数:
2.3.3.1 把 x.then 赋值给 then
2.3.3.2 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
2.3.3.3 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise,第二个参数叫做 rejectPromise:
2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
2.3.3.3.2 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
2.3.3.3.4 如果调用 then 方法抛出了异常 e:
2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
2.3.3.3.4.2 否则以 e 为据因拒绝 promise
2.3.3.4 如果 then 不是函数,以 x 为参数执行 promise
2.3.3 如果 x 不为对象或者函数,以 x 为参数执行 promise
如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise。
三,resolvePromise 方法
resolvePromise 介绍
如果 then 中方法的返回值 x 为 promsie 类型;
那么将根据返回值 x 决定 promise2 执行成功/失败处理;
根据 promise A+ 规范,创建 resolvePromise 方法,统一处理 Promise 的解析流程;
2,创建 resolvePromise
根据 then 中方法的返回值 x 决定 promise2 走成功/失败(调用 resolve 或 reject);
所以,resolvePromise
方法中入参需要promise2
,返回值x
,promise2 的resolve
和reject
:
3,使用 resolvePromise
在上一篇中,当 then 中方法返回值 x 为普通值时,调用成功处理 resolve(x);
resolvePromise
方法要统一处理返回值 x 的各种情况,所以先使用resolvePromise
替换掉resolve(x)
:
4,promise2 的初始化问题
1,问题分析
代码修改之后,运行将会报错:
报错:“不能访问 promise2 在初始化之前”;
原因:在 new promise 时,promise2 还没有完成初始化,所以 resolvePromise 中不能访问到 promise2;
2,参考规范
在 Promise A+ 规范中,可以找到此问题的说明:
在当前的执行上下文栈中,onFulfilled 或 onRejected 是不能被直接调用的;
onFulfilled 或 onRejected 得是在当前事件循环后异步执行的;可以使用
setTimeout
、setImmediate
、MutationObserever
、process.nextTick
在 then 方法被调用后将创建一个新的栈;
3,问题解决
以 setTimeout 为例,使用 setTimeout 创建宏任务:
备注:
try...catch...只能捕捉在同步代码中发生的异常,所以需要放在 setTimeout 内部;
原理:
通过 setTimeout 创建了宏任务,会在下一个事件循环中被执行;
保证调用 resolvePromise 时,promise2 实例已经创建完成;
四,实现 resolvePromise
promise2 === x
的情况
1,参考规范
如果返回值 x 是 thenable 对象,就调用 then 方法,使用当前 promise 状态作为结果;
如果 promise 和 x 是相同对象,使用 TypeError 作为原因,拒绝这个 promise;
2,测试原生 Promise
现象
会抛出 TypeError 类型错误,promise 死循环;
原因
then 中方法返回 promise2,promise2 不会再次调用 resolve 或 reject;
3,功能实现
根据 Promise A+ 规范,代码实现:
4,测试手写 Promise
TypeError: 发生错误,与原生 Promise 表现一致;
2,x 为对象或函数的情况
1,参考规范
如果 x 是一个 promise,就采用它的状态:
如果 x 是一个对象或函数(有可能是 Promise);
2,代码实现:区分对象或函数/普通值
函数是 JS 的基本类型,可以通过typeof
直接判断:
3,x.then 异常的情况
备注: 当返回值 x 是对象或函数时,不一定是 Promise;比如:返回{}
空对象;
1,参考规范
let then = x.then;
x.then 时可能发生异常,用此异常作为 promise 拒因;
2,代码实现:处理 x.then 异常
4,then 是否为函数的情况
1,参考规范
如果 then 是一个函数,调用它并让 x 作为它的 this;
2,代码实现:处理 then 是否为函数的 2 种情况
5,x 为 promise 的情况
终点:当 x.then 为 function 时,就认为 x 是 Promise 了;
接下来,就调用 x.then,由它的结果决定执行成功或失败处理;
1,参考规范
调用 then,并用 x 作为它的 this
第一个参数是 resolvePromise,第二个参数是 rejectPromise
resolvePromise 的值叫做 y;rejectPromise 的值叫做 r;
2,代码实现:调用 then,决定 promise2 成功/失败
判断是否为 Promise,如果是就它的执行 then 方法,决定 promise2 成功/失败;
五,结尾
本篇,主要实现了 Promise 对返回值 x 的处理,主要涉及到以下几个点:
回顾 Promise A+ 规范;
根据规范实现 resolvePromise 方法;
下一篇,完善 Promise 通过 promise-aplus-tests 测试;
评论