一,前言
上篇,实现了 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 方法
1,resolvePromise 介绍
如果 then 中方法的返回值 x 为 promsie 类型;
那么将根据返回值 x 决定 promise2 执行成功/失败处理;
根据 promise A+ 规范,创建 resolvePromise 方法,统一处理 Promise 的解析流程;
2,创建 resolvePromise
根据 then 中方法的返回值 x 决定 promise2 走成功/失败(调用 resolve 或 reject);
所以,resolvePromise
方法中入参需要promise2
、返回值x
、以及 promise2 的resolve
和reject
:
function resolvePromise(promise2, x, resolve, reject) {
// 根据返回值 x 的情况,调用 promise2 的 resolve 或 reject 函数
}
3,使用 resolvePromise
在上一篇中,当 then 中方法返回值 x 为普通值时,调用成功处理 resolve(x);
resolvePromise
方法要统一处理返回值 x 的各种情况,所以先使用resolvePromise
替换掉resolve(x)
:
/**
* 根据返回值 x 情况,调用 promise2 的 resolve 或 reject
*/
function resolvePromise(promise2, x, resolve, reject) {
resolve(x);// 返回值 x 为普通值的情况
}
class Promise{
constructor(executor){...}
then(onFulfilled, onRejected){
let promise2 = new Promise((resolve, reject)=>{
if(this.state === DULFILLED){
try{
let x = onFulfilled(this.value)
// resolve(x) 由 resolvePromise 进行统一处理
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e)
}
}
if(this.state === REJECTED){
try{
let x = onRejected(this.reason)
// resolve(x) 由 resolvePromise 进行统一处理
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e)
}
}
if(this.state === PENDING){
this.onResolvedCallbacks.push(()=>{
try{
let x = onFulfilled(this.value)
// resolve(x) 由 resolvePromise 进行统一处理
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e)
}
})
this.onRejectedCallbacks.push(()=>{
try{
let x = onRejected(this.reason)
// resolve(x) 由 resolvePromise 进行统一处理
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e)
}
})
}
});
return promise2;
}
}
module.exports = Promise;
4,promise2 的初始化问题
1,问题分析
代码修改之后,运行将会报错:
Cannot access 'promise2' before initialization
- 报错:“不能访问 promise2 在初始化之前”;
- 原因:在 new promise 时,promise2 还没有完成初始化,所以 resolvePromise 中不能访问到 promise2;
2,参考规范
在 Promise A+ 规范中,可以找到此问题的说明:
- 在当前的执行上下文栈中,onFulfilled 或 onRejected 是不能被直接调用的;
- onFulfilled 或 onRejected 得是在当前事件循环后异步执行的;可以使用
setTimeout
、setImmediate
、MutationObserever
、process.nextTick
在 then 方法被调用后将创建一个新的栈;
3,问题解决
以 setTimeout 为例,使用 setTimeout 创建宏任务:
then(onFulfilled, onRejected){
let promise2 = new Promise((resolve, reject)=>{
if(this.state === DULFILLED){
setTimeout(() => { // 创建宏任务
try{
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e)
}
}, 0);
}
if(this.state === REJECTED){
setTimeout(() => { // 创建宏任务
try{
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e)
}
}, 0);
}
if(this.state === PENDING){
this.onResolvedCallbacks.push(()=>{
setTimeout(() => { // 创建宏任务
try{
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e)
}
}, 0);
})
this.onRejectedCallbacks.push(()=>{
setTimeout(() => { // 创建宏任务
try{
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e)
}
}, 0);
})
}
});
return promise2;
}
备注:
- try…catch…只能捕捉在同步代码中发生的异常,所以需要放在 setTimeout 内部;
原理:
- 通过 setTimeout 创建了宏任务,会在下一个事件循环中被执行;
- 保证调用 resolvePromise 时,promise2 实例已经创建完成;
四,实现 resolvePromise
1,promise2 === x
的情况
1,参考规范
- 如果返回值 x 是 thenable 对象,就调用 then 方法,使用当前 promise 状态作为结果;
- 如果 promise 和 x 是相同对象,使用 TypeError 作为原因,拒绝这个 promise;
2,测试原生 Promise
let promise2 = new Promise((resolve, reject) => {
resolve(1);
}).then(() => {
return promise2; promise2 即为 then 中方法的返回值 x
})
promise2.then(data => {
console.log(data);
}, err => {
console.log(err);
})
// 报错:[TypeError: Chaining cycle detected for promise #<Promise>]
现象
- 会抛出 TypeError 类型错误,promise 死循环;
原因
- then 中方法返回 promise2,promise2 不会再次调用 resolve 或 reject;
3,功能实现
根据 Promise A+ 规范,代码实现:
function resolvePromise(promise2, x, resolve, reject){
// promise2 === x,promise 为拒绝状态 TypeError 作为拒因
if(promise2 === x){// 相同对象:判断引用地址相同
return reject(new TypeError('发生错误'))
}
}
4,测试手写 Promise
TypeError: 发生错误
与原生 Promise 表现一致;
2,x 为对象或函数的情况
1,参考规范
- 如果 x 是一个 promise,就采用它的状态:
- 如果 x 是一个对象或函数(有可能是 Promise);
2,代码实现:区分对象或函数/普通值
函数是 JS 的基本类型,可以通过typeof
直接判断:
function resolvePromise(promise2, x, resolve, reject) {
// ...
// 返回值 x 是对象或函数的情况
if((typeof x === 'object' && x !== null) || typeof x === 'function'){
}else{// 返回值 x 是普通值的情况
resolve(x) // 普通值直接调用 resolve(x)
}
}
3,x.then 异常的情况
备注: 当返回值 x 是对象或函数时,不一定是 Promise;比如:返回{}
空对象;
1,参考规范
- let then = x.then;
- x.then 时可能发生异常,用此异常作为 promise 拒因;
2,代码实现:处理 x.then 异常
function resolvePromise(promise2, x, resolve, reject) {
// ...
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// 若取 x.then 时发生异常,使用 e 作为拒因
try {
let then = x.then;
} catch (e) {
reject(e);
}
} else {
resolve(x)
}
}
4,then 是否为函数的情况
1,参考规范
- 如果 then 是一个函数,调用它并让 x 作为它的 this;
2,代码实现:处理 then 是否为函数的 2 种情况
function resolvePromise(promise2, x, resolve, reject) {
// ...
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
let then = x.then;
if(typeof then === 'function'){
// then 是函数:就认为 x 为 Promise 类型
}else{
resolve(x) // 按普通值处理
}
} catch (e) {
reject(e);
}
} else {
resolve(x)
}
}
5,x 为 promise 的情况
终点:当 x.then 为 function 时,就认为 x 是 Promise 了;
接下来,就调用 x.then,由它的结果决定执行成功或失败处理;
1,参考规范
- 调用 then,并用 x 作为它的 this
- 第一个参数是 resolvePromise,第二个参数是 rejectPromise
- resolvePromise 的值叫做 y;rejectPromise 的值叫做 r;
2,代码实现:调用 then,决定 promise2 成功/失败
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('发生错误'))
}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
let then = x.then;
if(typeof then === 'function'){
// 返回值 x 为 Promise,调用 then 看结果
then.call(x, y => {
resolve(y) // 成功
}, r => {
reject(r) // 失败
});
}else{
resolve(x)
}
} catch (e) {
reject(e);
}
} else {
resolve(x)
}
}
判断是否为 Promise,如果是就它的执行 then 方法,决定 promise2 成功/失败;
五,结尾
本篇,主要实现了 Promise 对返回值 x 的处理,主要涉及到以下几个点:
- 回顾了 Promise A+ 规范中的相关内容;
- 根据 Promise A+ 规范实现 resolvePromise 方法;
- 实现了对 then 中方法的返回值 x 为 promsie 类型的处理;
下一篇,完善 Promise 通过 promise-aplus-tests 测试;
维护记录
- 20211120
- 修改部分描述性语句;
- 修改结尾部分,更新文章摘要;