作为一个内卷了的前端小学生,将慢慢前进一直到小学毕业 Orz。
Promise
是我们日常工作或者面试常客,虽然很少有面试官让我们去实现一个
Promise
,但是假如我们能够从原理的层次去理解Promise A+
规范,那么我们将在面试中无往不利,工作中也能够更正确的使用,游刃有余。
我将从我个人学习的角度去实现一个最核心的Promise
,如果有错欢迎大家指正。
ps:文中代码都可以在node环境中运行
首先我们从使用入手,作为最基础的使用方式。
let promise = new Promise((resolve,reject) => {
resolve("success")
})
promise.then(value => {}, reason => {})
我们发现:
-
Promise
是一个类,在执行这个类的时候,需要传递一个执行器进去然后执行器会立即执行 -
Promise
中有三种状态fulfilled
rejected
pending
pending --> fulfilled | pending -->rejected
状态一旦确定就不可更改 -
resolve
和reject
函数式用来更改状态的 -
then
方法内部做的事情就是判断状态。如果状态成功调用成功的回调函数。反之亦然。 -
then
回调中的参数, 成功参数是成功的值, 失败参数是失败的原因
结合这几种最基本的使用方式我们可以从这几部分入手实现一个最基本的Promise
const [PENDING, FULFILLED, REJECTED] = ['pending', 'fulfilled', 'rejected']
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
status = PENDING //状态
value = undefined // 成功后的值
reason = undefined // 失败的原因
resolve = value => { //箭头函数的原因 将内部的 `this` 指向 promise 对象
// 如果程序不是等待,阻止程序向下运行
if (this.status !== PENDING) return
this.status = FULFILLED
// 保存成功后的值
this.value = value
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
}
then (successCallback, failCallback) {
if (this.status === FULFILLED) {
successCallback(this.value)
}else if (this.status === REJECTED) {
failCallback(this.reason)
}
}
}
module.exports = MyPromise
我们测试一下写的 MyPromise
是否符合我们的预期
const MyPromise = require("./MyPromise")
let promise = new MyPromise((resolve,reject) => {
resolve("success")
})
promise.then(value => {console.log(value)}, reason => {})
// 输出 success
我们发现结果是符合预期的, 我们的第一步的目的就达到了。 但是我们发现你的这个 MyPromise
并没有回调中异步的处理逻辑啊。No Problem 我们继续丰富我们的代码。
由于then
中加入了异步就导致无法第一时间 resolve
或者 reject
,那么这个时候就需要我们将异步的回调存储一份,等到 resolve
或者 reject
执行的时候再去处理。
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
status = PENDING //状态
value = undefined // 成功后的值
reason = undefined // 失败的原因
successCallback = undefined //成功的回调
failCallback = undefined // 失败的回调
resolve = value => { //箭头函数的原因 将内部的 `this` 指向 promise 对象
// 如果程序不是等待,阻止程序向下运行
if (this.status !== PENDING) return
this.status = FULFILLED
// 保存成功后的值
this.value = value
this.successCallback && this.successCallback(this.value)
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
this.failCallback && this.failCallback(this.reason)
}
then ( successCallback, failCallback) {
if (this.status === FULFILLED) {
successCallback(this.value)
}else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
this.successCallback = successCallback
this.failCallback = failCallback
}
}
}
我们再测试一下 then
中加入的异步的情况
const MyPromise = require("./MyPromise")
let promise = new MyPromise((resolve,reject) => {
setTimeout(() => {
resolve("成功")
}, 3000)
})
promise.then(value => {console.log(value)}, reason => {})
// 3秒后返回 成功
那么我们还知道 同一个 Promise
对象是可以被多次调用的,所以显而易见的是我们的successCallback
与failCallback
是一个数组的存在,每调用一次then
都需要往回调数组中push
一个值。所以这部分需要我们去稍微做一下改造。
const [PENDING, FULFILLED, REJECTED] = ['pending', 'fulfilled', 'rejected']
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
status = PENDING //状态
value = undefined // 成功后的值
reason = undefined // 失败的原因
successCallback = [] //成功的回调
failCallback = [] // 失败的回调
resolve = value => { //箭头函数的原因 将内部的 `this` 指向 promise 对象
// 如果程序不是等待,阻止程序向下运行
if (this.status !== PENDING) return
this.status = FULFILLED
// 保存成功后的值
this.value = value
while(this.successCallback.length) this.successCallback.shift()(this.value)
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while(this.failCallback.length) this.failCallback.shift()(this.reason)
}
then ( successCallback, failCallback) {
if (this.status === FULFILLED) {
successCallback(this.value)
}else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
}
}
module.exports = MyPromise
再次测试一下看一下结果
const MyPromise = require("./MyPromise")
let promise = new MyPromise((resolve,reject) => {
setTimeout(() => {
resolve("成功")
}, 3000)
})
promise.then(value => {console.log(`${value}-1`)}, reason => {})
promise.then(value => {console.log(`${value}-2`)}, reason => {})
promise.then(value => {console.log(`${value}-3`)}, reason => {})
// 成功-1
// 成功-2
// 成功-3
这个时候在MyPromise
中无论是同步还是异步,都可以去满足我们的使用条件。
现在重点来了,我们知道Promise
是链式调用的,这部分再我们以前的文章里提过(《异步编程》),感兴趣的同学可以去看一下。其中最重要的一点是Promise
的then
的返回都是一个全新的Promise
对象, 结合着这几点我们再去完善一下我们的实现。
const [PENDING, FULFILLED, REJECTED] = ['pending', 'fulfilled', 'rejected']
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
status = PENDING //状态
value = undefined // 成功后的值
reason = undefined // 失败的原因
successCallback = [] //成功的回调
failCallback = [] // 失败的回调
resolve = value => { //箭头函数的原因 将内部的 `this` 指向 promise 对象
// 如果程序不是等待,阻止程序向下运行
if (this.status !== PENDING) return
this.status = FULFILLED
// 保存成功后的值
this.value = value
while (this.successCallback.length) this.successCallback.shift()(this.value)
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while (this.failCallback.length) this.failCallback.shift()(this.reason)
}
then(successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => { //假如setTimeout的原因是造成异步, 因为我们本身代码是在new Promise的过程中,promise 还不是一个完整的promise对象
let x = successCallback(this.value)
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接用resolve传递下去
// 如果是promise对象,需要先查看一下是不是跟本身的promise对象相同,如果是那么需要抛出类型错误
// 如果不同 就去查看 新promise对象的返回结果
// 根据结果, 决定调用 resolve 还是 reject
resolvePromise(promise2, x, resolve, reject) //将上个then返回值传递给下个then, 假如是普通值可以直接传递给下个then
}, 0)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
})
return promise2
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError("Chaining cycle detected for Promise #<Promise>"))
}
if (x instanceof MyPromise) {
// promise 对象
// x.then(value => resolve(x), reason => reject(reason))
x.then(resolve, reject) //等同上面
} else {
resolve(x)
}
}
module.exports = MyPromise
这次我们发现,多了一个resolvePromise
的函数,这个函数的根本目的是为了根据不同的返回值,区分不同的处理情况,很显然如果是个值我们可以直接resolve
出去;如果是个promise
对象就需要我们去根据不同的返回调用不同的方法。
除此之外,我们仍然需要注意的一点是,因为then
返回的是一个新的Promise
对象,这就需要我们去单独做一层检测。这个检测中setTimeout()
是一个关键,由于我们的then
的返回逻辑是写在Promise
对象的立即执行的执行器中,还处在Promise
对象的生成阶段,当我们resolvePromise
传入promise2
的时候,并不是我们想得到的Promise
对象,需要我们加入一个异步,去等待整个实例化的执行结果,从而去检测是否存在循环调用。循环调用的报错是类型报错,因此需要用TypeError
去做错误的实例化。
我们执行以下看一下是否符合我们的预期
const MyPromise = require("./MyPromise")
let promise = new MyPromise((resolve,reject) => {
// setTimeout(() => {
resolve("成功")
// }, 3000)
})
let p1 = promise
.then(value => {
console.log(value)
return p1
})
p1.then(value => console.log(value), reason => console.log(reason.message))
// 成功
// Chaining cycle detected for Promise #<Promise>
很显然链式调用可行, 并且能够检测出循环调用。NICE!
我们在之前的实现过程中,只处理了 状态为 fulfilled
的链式调用的相关逻辑,现在我们补充一下 rejected
以及 pending
的关于链式调用的代码。
另外,因为then
方法还有一个特性,如果没有then
方法没有参数,那么它会将参数一直向后传递,知道碰到一个有参数的then
方法为止。大概意思就是
let promise = new MyPromise((resolve,reject) => {
resolve("成功")
})
promise.then().then().then(value => console.log(value))
// 成功
那么我补充一下这部分的实现
const [PENDING, FULFILLED, REJECTED] = ['pending', 'fulfilled', 'rejected']
class MyPromise {
constructor(executor) {
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
status = PENDING //状态
value = undefined // 成功后的值
reason = undefined // 失败的原因
successCallback = [] //成功的回调
failCallback = [] // 失败的回调
resolve = value => { //箭头函数的原因 将内部的 `this` 指向 promise 对象
// 如果程序不是等待,阻止程序向下运行
if (this.status !== PENDING) return
this.status = FULFILLED
// 保存成功后的值
this.value = value
while (this.successCallback.length) this.successCallback.shift()()
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while (this.failCallback.length) this.failCallback.shift()()
}
then(successCallback, failCallback) {
successCallback = successCallback ? successCallback : value => value //返回一个 `function` (value) => return value
failCallback = failCallback ? failCallback : reason => {throw reason}
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => { //假如setTimeout的原因是造成异步, 因为我们本身代码是在new Promise的过程中,promise 还不是一个完整的promise对象
try {
let x = successCallback(this.value)
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接用resolve传递下去
// 如果是promise对象,需要先查看一下是不是跟本身的promise对象相同,如果是那么需要抛出类型错误
// 如果不同 就去查看 新promise对象的返回结果
// 根据结果, 决定调用 resolve 还是 reject
resolvePromise(promise2, x, resolve, reject) //将上个then返回值传递给下个then, 假如是普通值可以直接传递给下个then
} catch (error) {
reject(err)
}
}, 0)
} else if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = failCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(err)
}
}, 0)
} else {
this.successCallback.push(() => {
setTimeout(() => {
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(err)
}
}, 0)
})
this.failCallback.push(() => {
setTimeout(() => {
try {
let x = failCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(err)
}
}, 0)
})
}
})
return promise2
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError("Chaining cycle detected for Promise #<Promise>"))
}
if (x instanceof MyPromise) {
// promise 对象
// x.then(value => resolve(x), reason => reject(reason))
x.then(resolve, reject) //等同上面
} else {
resolve(x)
}
}
module.exports = MyPromise
需要注意的一点是,我们为了达到一直向后传递参数的目的,我们要将参数为空的successCallback
跟failCallback
伪造一下。
-
successCallback
->value => value
-
failCallback
->reason => throw reason
截止到这里,我们就想Promise
最核心的部分给实现了。
现在我们总结一下:
Promise
是一个类,实例化的时候需要传一个立即执行的执行器Promise
有fulfilled
rejected
pending
3种状态Promise
的then
方法可以被多次调用- 对于执行器中的异步操作,需要将回调函数存储下来,待异步函数的回调结束后执行
Promise
的then
方法可以链式调用,并且返回的是新的Promise
对象Promise
的then
方法在没有传参数的情况下,可以将原本该有的状态和参数向下传递