浅显易懂的实现Promise之resolvePromise篇,Promise大收官

2022-12-18 0 724

序言

上周五皮包传授了 Promise 的此基础篇 同时实现,第一集该文著眼于 Promise 的核心理念部份 —— Promise Resolution Procedure。

在该文的最已经开始,蒋以呵呵皮包对 resolvePromise 表达式的认知。

此基础篇部份他们处置了此基础 Promise 机能,触发器方法论,拉艾初始化,彼时拉艾初始化他们是这种处置的:

浅显易懂的实现Promise之resolvePromise篇,Promise大收官

then 方式中依照 promise 状况分为了四种情形,五种情形,继续执行反弹表达式后的codice x,间接做为 promise 的获得成功值。

看见这儿,你假如会造成困惑,何况大部份类别的codice x 都间接回到吗?

总之并非,Promises/A+ 特别针对codice x 有比较复杂的处置,这也是责任编辑要传授的 The Promise Resolution Procedure。

他们从此基础篇的同时实现标识符也能辨认出,四种情形的处置标识符是度类似于的,假如再重新加入对codice x 多种不同情形的处置,标识符苦不堪言想象,因而他们将这部份方法论释放出出成 resolvePromise 方式。

resolvePromise 方式拒绝接受五个模块:

resolvePromise(promise2, x, resolve, reject);

promise2 是 then 方式codice,x 是 then 反弹表达式的codice,他们依照codice x 的情形,下定决心初始化 resolve 或 reject。

codice x

在阅读 Promises/A+ 规范之前,他们先来思考呵呵codice x 会有那些情形:

x 是个普通值(原始类别)x 是基于 Promises/A+ 规范 promise 对象x 是基于其他规范的 promise 对象x 是 promise2 (x 与 promise2 指向同一对象)

codice x 的情形是比较复杂的,他们接下来来看呵呵 Promises/A+ 规范是如何处置上述多种不同情形。

Promises/A+ 规范解读

Promises/A+ 规范洋洋洒洒的写了很大篇幅:

浅显易懂的实现Promise之resolvePromise篇,Promise大收官

下面咱们来认知上述规范到底讲述了什么。

Promise 解析过程是一个抽象操作,模块值为 promise 和 value,他们将其表示为 [[Resolve]](promise,x)

这儿与上面讲述的 resolvePromise 是相通的,只不过为了标识符编写,他们同时将 promise2 的 resovle 及 reject 方式做为模块传入。

假如 x 可 thenable,那么他们就认为 x 是一个类 promise 对象,它会尝试让 promise 采用 x 的状况。只要它们符合 Promises/A+ 的 then 方式,允许 promise 对 thenable 的处置进行互操作。

promise 有很多规范,Promises/A+ 只是其中之一,Promises/A+ 希望自身能兼容其他规范下的 promise 对象,判断依据为该对象是否可 thenable。

假如 promise 和 x 引用的同一对象,则以 TypeError 理由拒绝 (避免循环引用)

什么情形下会出现这种现象呐?看这种一个栗子:

const p = new Promise((resolve, reject) => { resolve(1); }); const promise2 = p.then((data) => { return promise2; }); // TypeError: Chaining cycle detected for promise #<Promise>promise2.then(null, (err) => console.log(err));

2. 假如 x 是一个 promise ,采用它的状况

假如 x 状况是 pending ,则 promise 也需要保持 pending 状况直至 x 状况转变为 fulfilled 或 rejected假如 x 状况是 fulfilled ,则以同样的值完成 promise假如 x 状况是 rejected ,则以同样的原因拒绝 promise

上面规范指出了当codice x 为 promise 对象时,他们假如如何处置,但并没有给出如何判断codice x 是否为 promise 对象

3. x 是一个对象或者表达式

声明 then 其值为 x.then假如检索属性 x.then 导致抛出异常 e,则以 e 为拒绝原因拒绝 promise。如果 then 是一个表达式,x 做为 then 的 this 初始化该方式,第一个模块是获得成功的反弹表达式,第二个模块是失败的反弹表达式—— 判断是否为 promise 的最小判断 假如获得成功反弹以值 y 初始化,运行 [[Resolve]](promise,y)假如失败反弹以原因 r 初始化,用 r 拒绝 promise假如获得成功反弹与失败反弹都被初始化或多次初始化同一个模块,则第一个初始化优先,其他初始化都将被忽略。如果初始化 then 方式抛出异常 e: 若获得成功反弹或失败反弹都初始化过,忽略未初始化,用 e 做为原因拒绝 promise 假如 then 并非表达式,用 x 做为值完成 promise

4. 假如 x 既并非对象也并非表达式,使用 x 做为值完成 promise

他们已经解读规范完毕,皮包下面提出几个问题,加深呵呵大家对 Promise Resolution 的认知。

问题

检索 x.then 属性会存在异常情形,你能举个类似于栗子吗?

规范考虑的非常全面,由于 Promises/A+ 规范能兼容其他具有 thenable 能力的 promise 同时实现,假设这种一个场景:

// 某个人非常变态 // 它给对象x的then属性的getter设置为报错const x = {}; Object.defineProperty(x, “then”, { get() { throw Error(“cant execute then function”); }, }); // 此时假如在初始化 x.then 就会抛出异常 // Uncaught Error: cant execute then function x.then;

2. 为什么 then 方法通过 call 初始化,而非 x.then 初始化?

then 属性已经被检索获得成功,假如再次检索,会存在一定风险。还是上面那个栗子,他们稍微改呵呵:

let n = 0; const x = {}; Object.defineProperty(x, “then”, { get() { // 修改成第2次初始化抛出异常 n++; if (n >= 2) { throw Error(“cant execute then function”); } return “success”; }, }); // success x.then; // Uncaught Error: cant execute then function x.then;

then.call(x) 与 x.then 效果相同,而且通过 then.call(x) 能减少二次检索的风险。

3. 假如获得成功反弹以值 y 初始化,运行 [[Resolve]](promise, y) ,这条规范啥意思?

皮包想了很久,终于想通了这儿,已经开始皮包误以为此条规范特别针对了两种 onfulfilled 情形:

onFulfilled 表达式回到 Promise 实例onFulfilled 表达式继续执行时模块为 Promsie 实例。

但经过对比思考,第二种情形是根本无法达到此规范。皮包还是对第二种非常好奇,于是去反复翻阅了 Promises/A+ 规范,辨认出这竟然是规范的漏网之鱼,规范没有提到这点的处置。但我通过在浏览器进行尝试,辨认出对于第二种情形,ES6 同样会对此情形递归解析(有机会皮包会单独写该文对比这两种情形)

对于onFulfilled 回到 Promise 实例,皮包来举个栗子:

const p1 = new Promise((resolve, reject) => { resolve(“i am p1”); }); const p2 = new Promise((resolve, reject) => { resolve(“i am p2”); }); const p3 = p2.then((res) => { console.log(res); return p1; }); // { // [[Prototype]]: Promise // [[PromiseState]]: “fulfilled”// [[PromiseResult]]: “i am p1” // } console.log(p3); // false console.log(p3 === p1);

从输出结果能辨认出,p2 的 then 方式codice为 p1 ,回到的是全新的 Promise 实例,与 p1 不同,只不过采用了 p1 状况。

4. 假如获得成功反弹与失败反弹都被初始化或多次初始化同一个模块,则第一个初始化优先,其他初始化都将被忽略。这条规范又是在处置什么情形?

看见这条规范,你可能会很奇怪,因为咱们手写的 Promise 在此基础篇已经处置过当前情形,获得成功与失败反弹只会初始化其中之一。 Promises/A+ 规范中多次提到,能兼容其他具备 thenable 能力的 promise 对象,其他规范同时实现的 promise 实例未必会处置此情形,因而此条规范是为了兼容其他不完善的 promise 实现。

源码同时实现

循环引用

假如 promise 和 x 引用的同一对象,则以 TypeError 理由拒绝。因而他们需要给 resolvePromise 添加一步校验:

const resolvePromise = function (promise, x, resolve, reject) { // 循环引用,自己等待自己完成 if (promise === x) { // 用一个类别错误,结束掉 promise return reject( new TypeError( `TypeError: Chaining cycle detected for promise #<myPromise> ` ) ); } };

判断 x 是否为 promise 实例

通过上面规范的解读,他们能把判断 x 是否为 promise 实例归结为以下步骤:

promise 实例假如是对象或者表达式: 首先判断 x 是否为对象或表达式promise 对象必须具备 thenable 能力: 接着检索 x.then 属性then 属性假如是个可继续执行的表达式: 最后判断 then 是否为表达式 (这是最小判断)假如上述都满足,Promises/A+ 就认为 x 是一个 promise 实例

精炼呵呵: 首先判断 x 是否为对象或表达式;然后判断 x.then 是否为表达式

他们来编写呵呵这部份标识符:

function resolvePromise(promise, x, resolve, reject) { // 判断 x 是否为对象(排除null情形)或表达式 if ((typeof x === “object” && x !== null) || typeof x === “function”) { // 检索 x.then 可能会抛出异常 try { // 检索 x.then let then = x.then; // 判断 then 是否为表达式 // 这是最小判断,满足此条件后,认定为 promise 实例 if (typeof then === “function”) { // 继续执行 x.then 会再次检索 then 属性,有风险发生错误 // 这儿的另外两个模块后面会详细传授 then.call(x); } else { // then 方式并非表达式,为普通值——{then:123} resolve(x); } } catch (e) { // 存在异常,继续执行 reject(e) reject(e); } } else { // 既并非对象也并非表达式,说明是普通值,调用 resolve(x) 完成 resolve(x); } }

codice x 为 promise

上文他们已经对此条规范做了详细的解析,但假如如何同时实现此条规范呐?非常简单,他们只需对 then.call(x) 略作修改即可。

then.call( x, (y) => { // 假如x是一个 promise 就用他的状况来下定决心 走获得成功还是失败 resolvePromise(promise, y, resolve, reject); //递归解析y的值 }, (r) => { // 一旦失败了 就不在解析失败的结果了 reject(r); } );

不知道大家能不能认知上述递归的原理?皮包给举个栗子。

// 假设 y 是 promise,就以下面为例const y = new Promise((resolve, reject) => { resolve(1); }); // 经过 resolvePromise resolvePromise(promise, y, resolve, reject);

resolvePromise(promise, y, resolve, reject) 的继续执行流程是这种的:

经过一系列判断,最终通过 y.then 为表达式判断出 y 为 promise继续执行 then(y, resolvePromsie, rejectPromise)上面标识符等同于继续执行下面标识符
y.then( (y1) => { resolvePromise(promise, y1, resolve, reject); }, (r1) => { reject(r1); } );
y1 的值是 1,为普通值,因而间接初始化 resolve(y1)因而同时实现了 promise2 采纳codice x 的状况

兼容不完善的 promise 同时实现

为了兼容不完善的 promise 同时实现,因而他们需要给 resolvePromise 中继续执行添加一个锁。

function resolvePromise(promise, x, resolve, reject) { // 判断 x 是否为对象(排除null情形)或表达式 let called = false; if ((typeof x === “object” && x !== null) || typeof x === “function”) { try { let then = x.then; if (typeof then === “function”) { then.call( x, (y) => { // 添加锁,避免获得成功后继续执行失败 if (called) return; called = true; resolvePromise(promise, y, resolve, reject); }, (r) => { // 添加锁,避免失败后继续执行获得成功 if (called) return; called = true; reject(r); } ); } else { resolve(x); } } catch (e) { // 添加锁,避免失败后继续执行获得成功 if (called) return; called = true; reject(e); } } else { // 既并非对象也并非表达式,说明是普通值,初始化 resolve(x) 完成 resolve(x); } }

then 方式修改

该文最已经开始他们提到将 then 方式四种情形标识符重复度过高,他们将此部份释放出为 resolvePromise ,第一个模块为 promise2。

他们取出此基础篇 then 方式部份标识符,重点关注 resolvePromise 初始化部份。你假如很容易问题,promise2 是 then 整体继续执行完毕后才能访问,resolvePromise 此时假如是无法访问到该方式。

then(onFulfilled, onRejected) { let promise2 = new Promise((resolve, reject) => { if (this.status === FULFILLED) { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } } }); return promise2; }

因而他们需要给 resolvePromise 加呵呵触发器操作,本手写使用 setTimeout 同时实现。

then(onFulfilled, onRejected) { let promise2 = new Promise((resolve, reject) => { if (this.status === FULFILLED) { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0) } }); return promise2; }

同时实现到这儿,手写 Promise 就全部剧终了,下面他们来测试呵呵他们的手写 Promise 是否能通过 Promises/A+ 提供的案例测试。

完整版 promise 标识符: 手写 Promise 完全版

案例测试

延迟对象

在他们的手写 Promise 中添加 deferred 部份标识符:

Promise.deferred = function () { let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd; };

promises-aplus-tests

使用 npm 安装 promises-aplus-tests。

npm i promises-aplus-tests

然后进入到待测试的 promise 文件夹,继续执行

promises-aplus-tests promise.js
浅显易懂的实现Promise之resolvePromise篇,Promise大收官

测试通过,大功告成!

后语

我是 战场皮包 ,一个快速成长中的小前端,希望能和大家一起进步。

假如喜欢皮包,能在 掘金[1] 关注我,同样也能关注我的小小公众号——**皮包学前端**。

一路加油,冲向未来!!!

疫情早日结束 人间恢复太平

参考资料

[1]

https://juejin.cn/user/4424090519078430: https://juejin.cn/user/4424090519078430

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务