序言
许多 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 总共分成四个状况:
⏳pending:等候中,这是 Promise 的初始状况; ♂️fulfilled:已完结,恒定初始化 resolve 的状况; ♂️rejected:已拒绝,内部出现错误,或者是初始化 reject 之后的状况;
他们可以看到 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 内部还有个结果 [[PromiseResult]],用来暂存 resolve/reject 拒绝接受的值。
继续在缺省中添加三个内部结果。
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)
}
)
此时,控制台只会打印出 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 形式中的反弹会被放置到微任务队列中,然后触发器初始化。
所以,他们需要将反弹的初始化放入触发器队列,这儿他们可以放到 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,这一点他们可以透过下面代码验证呵呵。
可以看到 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 秒后,控制台出现如下结果:
可以看到,这基本符合他们的预期。
值穿透
假如他们在初始化 then 的这时候,假如没传至任何的参数,按照规范化,当前 promise 的值是可以透传到下三个 then 形式的。例如,如下代码:
new Deferred(resolve => {
resolve(1)
})
.then()
.then()
.then(val => {
console.log(val)
})
在控制台并没看到任何输出,而切换到 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 {
// …
}
}
}
现在他们早已可以拿到正确结果了。
一步棋之遥
现在他们距离完美同时实现 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 第一类,则被认为是循环引用,具体案例如下:
then 形式回到的新的 promise 第一类 p1,在反弹中被当做回到值,此时会抛出三个异常。因为按照之前的逻辑,代码将会一直困在这一段逻辑里。
所以,我们需要提前预防,及时抛出错误。
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)
})
上面的结果也是完美符合他们的预期。
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)
})
上面的运行结果,基本符合他们的预期。
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 类就早已同时实现了,这儿却是省略了部分细节,完整代码可以访问github。Promise 的出现为后期的 async 句法打下了坚实基础,下一篇博客可以好好聊一聊 JavaScript 的触发器编程史,不小心又给自己挖坑了。。。
往期推荐:
零基础学习前端需要掌控的技术和相关教程
我是如何在自学编程9个月后找到工作的
❤️ 感谢我们
假如你觉得这篇内容对你挺有有帮助的话:
点赞全力支持下吧,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)
原文作者:Shenfq
原文链接:https://juejin.im/post/6867690497545076743