手把手教你实现 Promise

2023-01-11 0 489

序言

许多 JavaScript 的新手都曾体会过被反弹冥界主宰的绝望,直到掌控了 Promise 句法才算证悟。尽管许多词汇都已然内建了 Promise ,但 JavaScript 中或者说将其发扬光大的却是 jQuery 1.5 对 $.ajax 的解构,全力支持了 Promise,所以用语也和 jQuery 尊崇的拉艾初始化大相迳庭。而后 ES6 降生,我们才早已开始步入幸福家庭 Promise 的黄金时代,再而后 ES8 又导入了 async 句法,让 JavaScript 的触发器读法更为典雅。

那时他们就一步棋一步棋来同时实现三个 Promise,假如你还没所用 Promise,提议先熟识呵呵 Promise 句法再来写作责任编辑。

缺省

在已近的 Promise/A+ 规范化中并没明确规定 promise 第一类何来,在 jQuery 中透过初始化 $.Deferred() 获得 promise 第一类,ES6 中透过示例化 Promise 类获得 promise 第一类。这儿他们采用 ES 的句法,内部结构三个类,透过示例化的形式回到 promise 第一类,虽然 Promise 早已存有,他们暂给那个类取名 Deferred。

lass Deferred { constructor(callback) { const resolve = () => { // TODO } const reject = () => { // TODO } try { callback(resolve, reject) } catch (error) { reject(error) } } }

内部结构函数拒绝接受三个 callback,初始化 callback 的这时候需传至 resolve、reject 三个形式。

Promise 的状况

Promise 总共分成四个状况:

手把手教你实现 Promise
⏳pending:等候中,这是 Promise 的初始状况;
手把手教你实现 Promise
‍♂️fulfilled:已完结,恒定初始化 resolve 的状况;
手把手教你实现 Promise
‍♂️rejected:已拒绝,内部出现错误,或者是初始化 reject 之后的状况;
手把手教你实现 Promise

他们可以看到 Promise 在运行期间有三个状况,存储在 [[PromiseState]] 中。下面他们为 Deferred 添加三个状况。

//基础变量的定义 const STATUS = { PENDING: PENDING, FULFILLED: FULFILLED, REJECTED: REJECTED } class Deferred { constructor(callback) { this.status = STATUS.PENDING const resolve = () => { // TODO } const reject = () => { // TODO } try { callback(resolve, reject) } catch (error) { // 出现异常直接进行 reject reject(error) } } }

这儿还有个有意思的事情,早期浏览器的同时实现中 fulfilled 状况是 resolved,明显与 Promise 规范化不符。当然,现在早已修复了。

手把手教你实现 Promise

内部结果

除开状况,Promise 内部还有个结果 [[PromiseResult]],用来暂存 resolve/reject 拒绝接受的值。

手把手教你实现 Promise
手把手教你实现 Promise

继续在缺省中添加三个内部结果。

class Deferred { constructor(callback) { this.value = undefined this.status = STATUS.PENDING const resolve = value => { this.value = value // TODO } const reject = reason => { this.value = reason // TODO } try { callback(resolve, reject) } catch (error) { // 出现异常直接进行 reject reject(error) } } }

储存反弹

采用 Promise 的这时候,他们一般都会初始化 promise 第一类的 .then 形式,在 promise 状况转为 fulfilled 或 rejected 的这时候,拿到内部结果,然后做后续的处理。所以缺省中,还需要内部结构三个数组,用来存储 .then 形式传至的反弹。

class Deferred { constructor(callback) { this.value = undefined this.status = STATUS.PENDING this.rejectQueue = [] this.resolveQueue = [] const resolve = value => { this.value = value // TODO } const reject = reason => { this.value = reason // TODO } try { callback(resolve, reject) } catch (error) { // 出现异常直接进行 reject reject(error) } } }

resolve 与 reject

修改状况

接下来,他们需要同时实现 resolve 和 reject 三个形式,这三个形式在被初始化的这时候,会改变 promise 第一类的状况。所以任意三个形式在被初始化之后,另外的形式是无法被初始化的。

new Promise((resolve, reject) => { setTimeout(() => { resolve( ‍♂️) }, 500) setTimeout(() => { reject( ‍♂️) }, 800) }).then( () => { console.log(fulfilled) }, () => { console.log(rejected) } )
手把手教你实现 Promise

此时,控制台只会打印出 fulfilled,并不会出现 rejected。

class Deferred { constructor(callback) { this.value = undefined this.status = STATUS.PENDING this.rejectQueue = [] this.resolveQueue = [] let called // 用于判断状况是否被修改 const resolve = value => { if (called) return called = true this.value = value // 修改状况 this.status = STATUS.FULFILLED } const reject = reason => { if (called) return called = true this.value = reason // 修改状况 this.status = STATUS.REJECTED } try { callback(resolve, reject) } catch (error) { // 出现异常直接进行 reject reject(error) } } }

初始化反弹

修改完状况后,拿到结果的 promise 一般会初始化 then 形式传至的反弹。

class Deferred { constructor(callback) { this.value = undefined this.status = STATUS.PENDING this.rejectQueue = [] this.resolveQueue = [] let called // 用于判断状况是否被修改 const resolve = value => { if (called) return called = true this.value = value // 修改状况 this.status = STATUS.FULFILLED // 初始化反弹 for (const fn of this.resolveQueue) { fn(this.value) } } const reject = reason => { if (called) return called = true this.value = reason // 修改状况 this.status = STATUS.REJECTED // 初始化反弹 for (const fn of this.rejectQueue) { fn(this.value) } } try { callback(resolve, reject) } catch (error) { // 出现异常直接进行 reject reject(error) } } }

熟识 JavaScript 事件系统的同学应该知道,promise.then 形式中的反弹会被放置到微任务队列中,然后触发器初始化。

手把手教你实现 Promise

所以,他们需要将反弹的初始化放入触发器队列,这儿他们可以放到 setTimeout 中进行延迟初始化,尽管不太符合规范化,但将就将就。

class Deferred { constructor(callback) { this.value = undefined this.status = STATUS.PENDING this.rejectQueue = [] this.resolveQueue = [] let called // 用于判断状况是否被修改 const resolve = value => { if (called) return called = true // 触发器初始化 setTimeout(() => { this.value = value // 修改状况 this.status = STATUS.FULFILLED // 初始化反弹 for (const fn of this.resolveQueue) { fn(this.value) } }) } const reject = reason => { if (called) return called = true // 触发器初始化 setTimeout(() =>{ this.value = reason // 修改状况 this.status = STATUS.REJECTED // 初始化反弹 for (const fn of this.rejectQueue) { fn(this.value) } }) } try { callback(resolve, reject) } catch (error) { // 出现异常直接进行 reject reject(error) } } }

then 形式

接下来他们需要同时实现 then 形式,所用 Promise 的同学肯定知道,then 形式是能够继续进行拉艾初始化的,所以 then 必须要回到三个 promise 第一类。但在 Promise/A+ 规范化中,有明确的明确规定,then 形式回到的是三个新的 promise 第一类,而不是直接回到 this,这一点他们可以透过下面代码验证呵呵。

手把手教你实现 Promise

可以看到 p1 第一类和 p2 是三个不同的第一类,并且 then 形式回到的 p2 第一类也是 Promise 的示例。

除此之外,then 形式还需要判断当前状况,假如当前状况不是 pending 状况,则可以直接初始化传至的反弹,而不用再放入队列进行等候。

class Deferred { then(onResolve, onReject) { if (this.status === STATUS.PENDING) { // 将反弹放入队列中 const rejectQueue = this.rejectQueue const resolveQueue = this.resolveQueue return new Deferred((resolve, reject) => { // 暂存到成功反弹等候初始化 resolveQueue.push(function (innerValue) { try { const value = onResolve(innerValue) // 改变当前 promise 的状况 resolve(value) } catch (error) { reject(error) } }) // 暂存到失败反弹等候初始化 rejectQueue.push(function (innerValue) { try { const value = onReject(innerValue) // 改变当前 promise 的状况 resolve(value) } catch (error) { reject(error) } }) }) } else { const innerValue = this.value const isFulfilled = this.status === STATUS.FULFILLED return new Deferred((resolve, reject) => { try { const value = isFulfilled ? onResolve(innerValue) // 成功状况初始化 onResolve : onReject(innerValue) // 失败状况初始化 onReject resolve(value) // 回到结果给后面的 then } catch (error) { reject(error) } }) } } }

现在他们的逻辑早已可以基本跑通,他们先试运行一段代码:

new Deferred(resolve => { setTimeout(() => { resolve(1) }, 3000) }).then(val1 => { console.log(val1, val1) return val1 * 2 }).then(val2 => { console.log(val2, val2) return val2 })

3 秒后,控制台出现如下结果:

手把手教你实现 Promise

可以看到,这基本符合他们的预期。

值穿透

假如他们在初始化 then 的这时候,假如没传至任何的参数,按照规范化,当前 promise 的值是可以透传到下三个 then 形式的。例如,如下代码:

new Deferred(resolve => { resolve(1) }) .then() .then() .then(val => { console.log(val) })
手把手教你实现 Promise

在控制台并没看到任何输出,而切换到 Promise 是可以看到正确结果的。

手把手教你实现 Promise

要解决那个形式很简单,只需要在 then 初始化的这时候判断参数是否为三个函数,假如不是则需要给三个默认值。

const isFunction = fn => typeof fn === function class Deferred { then(onResolve, onReject) { // 解决值穿透 onReject = isFunction(onReject) ? onReject : reason => { throw reason } onResolve = isFunction(onResolve) ? onResolve : value => { return value } if (this.status === STATUS.PENDING) { // … } else { // … } } }
手把手教你实现 Promise

现在他们早已可以拿到正确结果了。

一步棋之遥

现在他们距离完美同时实现 then 形式只差一步棋之遥,那就是他们在初始化 then 方法传至的 onResolve/onReject 反弹时,还需要判断他们的回到值。假如反弹的内部回到的就是三个 promise 第一类,他们应该如何处理?或者出现了循环引用,他们又该怎么处理?

前面他们在拿到 onResolve/onReject 的回到值后,直接就初始化了 resolve 或者 resolve,现在他们需要把他们的回到值进行一些处理。

then(onResolve, onReject) { // 解决值穿透代码早已省略 if (this.status === STATUS.PENDING) { // 将反弹放入队列中 const rejectQueue = this.rejectQueue const resolveQueue = this.resolveQueue const promise = new Deferred((resolve, reject) => { // 暂存到成功反弹等候初始化 resolveQueue.push(function (innerValue) { try { const value = onResolve(innerValue) – resolve(value) + doThenFunc(promise, value, resolve, reject) } catch (error) { reject(error) } }) // 暂存到失败反弹等候初始化 rejectQueue.push(function (innerValue) { try { const value = onReject(innerValue) – resolve(value) + doThenFunc(promise, value, resolve, reject) } catch (error) { reject(error) } }) }) return promise } else { const innerValue = this.value const isFulfilled = this.status === STATUS.FULFILLED const promise = new Deferred((resolve, reject) => { try { const value = isFulfilled ? onResolve(innerValue) // 成功状况初始化 onResolve : onReject(innerValue) // 失败状况初始化 onReject – resolve(value) + doThenFunc(promise, value, resolve, reject) } catch (error) { reject(error) } }) return promise } }

回到值判断

在他们采用 Promise 的这时候,经常会在 then 形式中回到三个新的 Promise,然后把新的 Promise 完成后的内部结果再传递给后面的 then 形式。

fetch(server/login) .then(user => { // 回到新的 promise 第一类 return fetch(`server/order/${user.id}`) }) .then(order => { console.log(order) })
function doThenFunc(promise, value, resolve, reject) { // 假如 value 是 promise 第一类 if (value instanceof Deferred) { // 初始化 then 形式,等候结果 value.then( function (val) { doThenFunc(promise, value, resolve, reject) }, function (reason) { reject(reason) } ) return } // 假如非 promise 第一类,则直接回到 resolve(value) }

判断循环引用

假如当前 then 形式反弹函数回到值是当前 then 形式产生的新的 promise 第一类,则被认为是循环引用,具体案例如下:

手把手教你实现 Promise

then 形式回到的新的 promise 第一类 p1,在反弹中被当做回到值,此时会抛出三个异常。因为按照之前的逻辑,代码将会一直困在这一段逻辑里。

手把手教你实现 Promise

所以,我们需要提前预防,及时抛出错误。

function doThenFunc(promise, value, resolve, reject) { // 循环引用 if (promise === value) { reject( new TypeError(Chaining cycle detected for promise) ) return } // 假如 value 是 promise 第一类 if (value instanceof Deferred) { // 初始化 then 形式,等候结果 value.then( function (val) { doThenFunc(promise, value, resolve, reject) }, function (reason) { reject(reason) } ) return } // 假如非 promise 第一类,则直接回到 resolve(value) }

现在他们再试试在 then 中回到三个新的 promise 第一类。

const delayDouble = (num, time) => new Deferred((resolve) => { console.log(new Date()) setTimeout(() => { resolve(2 * num) }, time) }) new Deferred(resolve => { setTimeout(() => { resolve(1) }, 2000) }) .then(val => { console.log(new Date(), val) return delayDouble(val, 2000) }) .then(val => { console.log(new Date(), val) })
手把手教你实现 Promise

上面的结果也是完美符合他们的预期。

catch 形式

catch 形式其实很简单,相当于 then 形式的三个简写。

class Deferred { constructor(callback) {} then(onResolve, onReject) {} catch(onReject) { return this.then(null, onReject) } }

静态形式

resolve/reject

Promise 类还提供了三个静态形式,直接回到状况早已固定的 promise 第一类。

class Deferred { constructor(callback) {} then(onResolve, onReject) {} catch(onReject) {} static resolve(value) { return new Deferred((resolve, reject) => { resolve(value) }) } static reject(reason) { return new Deferred((resolve, reject) => { reject(reason) }) } }

all

all 形式拒绝接受三个 promise 第一类的数组,等数组中所有的 promise 第一类的状况变为 fulfilled,然后回到结果,其结果也是三个数组,数组的每个值对应的是 promise 第一类的内部结果。

首先,他们需要先判断传至的参数是否为数组,然后内部结构三个结果数组以及三个新的 promise 第一类。

class Deferred { static all(promises) { // 非数组参数,抛出异常 if (!Array.isArray(promises)) { return Deferred.reject(new TypeError(args must be an array)) } // 用于存储每个 promise 第一类的结果 const result = [] const length = promises.length // 假如 remaining 归零,表示所有 promise 第一类早已 fulfilled let remaining = length const promise = new Deferred(function (resolve, reject) { // TODO }) return promise } }

接下来,他们需要进行呵呵判断,对每个 promise 第一类的 resolve 进行拦截,每次 resolve 都需要将remaining减一,直到remaining归零。

class Deferred { static all(promises) { // 非数组参数,抛出异常 if (!Array.isArray(promises)) { return Deferred.reject(new TypeError(args must be an array)) } const result = [] // 用于存储每个 promise 第一类的结果 const length = promises.length let remaining = length const promise = new Deferred(function (resolve, reject) { // 如果数组为空,则回到空结果 if (promises.length === 0) return resolve(result) function done(index, value) { doThenFunc( promise, value, (val) => { // resolve 的结果放入 result 中 result[index] = val if (–remaining === 0) { // 假如所有的 promise 都早已回到结果 // 然后运行后面的逻辑 resolve(result) } }, reject ) } // 放入触发器队列 setTimeout(() => { for (let i = 0; i < length; i++) { done(i, promises[i]) } }) }) return promise } }

下面他们透过如下代码,判断逻辑是否正确。按照预期,代码运行后,在 3 秒之后,控制台会打印三个数组[2, 4, 6]。

const delayDouble = (num, time) => new Deferred((resolve) => { setTimeout(() => { resolve(2 * num) }, time) }) console.log(new Date()) Deferred.all([ delayDouble(1, 1000), delayDouble(2, 2000), delayDouble(3, 3000) ]).then((results) => { console.log(new Date(), results) })
手把手教你实现 Promise

上面的运行结果,基本符合他们的预期。

race

race 形式同样拒绝接受三个 promise 第一类的数组,但它只需要有三个 promise 变为 fulfilled 状况就会回到结果。

class Deferred { static race(promises) { if (!Array.isArray(promises)) { return Deferred.reject(new TypeError(args must be an array)) } const length = promises.length const promise = new Deferred(function (resolve, reject) { if (promises.length === 0) return resolve([]) function done(value) { doThenFunc(promise, value, resolve, reject) } // 放入触发器队列 setTimeout(() => { for (let i = 0; i < length; i++) { done(promises[i]) } }) }) return promise } }

下面他们将前面验证 all 形式的案例改成 race。按照预期,代码运行后,在 1 秒之后,控制台会打印三个2。

const delayDouble = (num, time) => new Deferred((resolve) => { setTimeout(() => { resolve(2 * num) }, time) }) console.log(new Date()) Deferred.race([ delayDouble(1, 1000), delayDouble(2, 2000), delayDouble(3, 3000) ]).then((results) => { console.log(new Date(), results) })
手把手教你实现 Promise

面的运行结果,基本符合他们的预期。

总结

三个简易版的 Promise 类就早已同时实现了,这儿却是省略了部分细节,完整代码可以访问github。Promise 的出现为后期的 async 句法打下了坚实基础,下一篇博客可以好好聊一聊 JavaScript 的触发器编程史,不小心又给自己挖坑了。。。

往期推荐:

零基础学习前端需要掌控的技术和相关教程

我是如何在自学编程9个月后找到工作的

❤️ 感谢我们

假如你觉得这篇内容对你挺有有帮助的话:

点赞全力支持下吧,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)

原文作者:Shenfq

原文链接:https://juejin.im/post/6867690497545076743

相关文章

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

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