从浏览器运行机制看异步,这下终于学会了Promise(二)手写Promise
接上篇,明白了异步逻辑,学会了原生promise使用之后,我们来手写实现一个简版Promise
五、动手实现一个简版Promise
5.1 开始之前
首先来分析一下我们需要做什么:
- 首先要新建一个Promise类,它接收一个执行器函数,并立即执行执行它
- Promise的实例有三种状态:
- pending
- fulfilled
- rejected
- Promise的实例有两种状态转变的可能
- pending👉fulfilled
- pending👉rejected
- Promise的实例有两个方法来改变其状态,分别是
resolve和reject - Promise的实例有一个then方法,它可以链式调用,并且根据Promise实例的状态判断调用两个回调函数
- Promise的实例有一个
catch方法,是then方法在拒绝状态的一种特殊的形式 - Promise有静态方法
Promise.all、Promise.race、Promise.resolve、Promise.reject
5.2 实现Promise基本结构
原生promise可以通过new来构造,因此这里我们选择类来实现
第一步:构建类和执行器
executor作为promise的执行器,它以resolve和reject作为参数,也就是我们原生Promise的参数
// 构建Promise类和excutor执行器
class MyPromise {
constructor(executor) {
// resolve
const resolve = () => {
console.log('resolve')
}
// reject
const reject = () => {
console.log('reject')
}
// 立即执行执行器函数,出错则调用reject捕获错误
try {
executor(resolve, reject)
} catch(error) {
reject(error)
}
}
}
// 检验是否能运行
const p = new MyPromise((resolve, reject) => {
console.log('test')
resolve()
reject()
})
// 打印结果如下,非常成功
// test
// resolve
// reject
第二步:加入状态管理
注意resolve和reject的作用可以理解为仅仅改变状态和暴露值/原因,他不会终止后面的代码运行,且状态只能进行一次不可逆的转变。
class MyPromise {
constructor(executor) {
// +++++ 初始化状态 +++++
this._status = STATUS.PENDING // promise初始状态为pending
this._value = void 0 // 调用then回调的第一个参数的返回值
this._reason = void 0 // 调用then回调的第二个参数的返回值
// +++++ resolve => 在状态为pending时,将状态改成 fulfilled +++++
const resolve = value => {
if (this._status === STATUS.PENDING) {
this._status = STATUS.FULFILLED // 更改状态
this._value = value // 储存当前值,用于then回调
}
}
// +++++ reject 在状态为pending时,将状态改成 rejected +++++
const reject = reason => {
if (this._status === STATUS.PENDING) {
this._status = STATUS.REJECTED
this._reason = reason
}
}
// 立即执行执行器函数,出错则调用reject捕获错误
// ......
}
}
// 测试运行
const p = new MyPromise((resolve, reject) => {
console.log('test')
resolve()
reject()
})
console.log(p)
// 状态也改变了,还只改变了一次,没问题
// test
// MyPromise {
// _status: 'fulfilled',
// _value: undefined,
// _reason: undefined
// }
第三步:简单then方法初步实现
then方法接受两个函数作为参数,并根据定型的结果判断调用哪个回调
// +++++ 类方法then +++++
then(onFulfilled, onRejected) {
// 对传入的参数进行检查,如果不是回调函数:在成功回调中直接返回,在失败回调中作为错误抛出
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected =
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason
}
// 根据状态判断调用哪个回调
if (this._status === STATUS.FULFILLED) {
// 成功时调用onFulfilled回调
onFulfilled(this._value)
} else if (this._status === STATUS.REJECTED) {
// 失败时调用onRejected回调
onRejected(this._reason)
}
}
以上实现了Promise的基本结构,生成promise实例时可以自动执行入参函数,实现了resolve和reject对状态的更改和then方法的回调调用,但并没有实现异步逻辑
5.2 加入异步逻辑
ES6有宏任务和微任务两种异步模式,promise的
then方法属于微任务,会在下一个宏任务前插队运行,我们这里统一使用setTimeout来模拟实现promise的异步逻辑
第一步:通过setTimeout实现then的异步
// 类方法then
then(onFulfilled, onRejected) {
// ......
// 根据状态判断调用哪个回调
if (this._status === STATUS.FULFILLED) {
// 成功时调用onFulfilled回调
// +++++ 通过setTimeout实现异步 +++++
setTimeout(() => onFulfilled(this._value), 0)
} else if (this._status === STATUS.REJECTED) {
// 失败时调用onRejected回调
// +++++ 通过setTimeout实现异步 +++++
setTimeout(() => onRejected(this._reason), 0)
}
}
第二步:解决定时器中定型情况,加入异步队列
给onFulfilled和onRejected加入异步逻辑后,如果执行器函数executor也通过异步的代码对promise进行定型呢?那么可能会出现状态还是pending就走到了then里,那么then里你写的在fulfilled状态和rejected状态下才会执行的代码可能永远跑不了
// 像这样,是不会resolve的
const p = new MyPromise((resolve, reject) => {
setTimeout(() => resolve(1), 1)
})
p.then(console.log, null) // 啥也不会打印
这里我们可以等到它在定型之后再去运行,但是考虑到我们可以对同一个promise使用不同的回调调用多次then方法,因此可以考虑使用数组来存储回调,我们需要加入两个队列,分别存储成功的回调和失败的回调
constructor(executor) {
// ...
// +++++ 记录回调的队列 +++++
this._resolveQueue = [] // resolve时触发的成功队列
this._rejectQueue = [] // reject时触发的失败队列
// ...
}
在then的回调中增加pending状态的处理
then(onFulfilled, onRejected) {
// ...
// +++++ 如果是pending状态则加入队列 +++++
if (this._status === STATUS.PENDING) {
this._onFulfilledQueue.push(onFulfilled)
this._onRejectedQueue.push(onRejected)
}
// ...
}
第三步:在resolve和reject改变状态后,处理异步回调队列
这里加入了回调队列的调用
// resolve => 在状态为pending时,将状态改成 fulfilled
const resolve = value => {
if (this._status === STATUS.PENDING) {
this._status = STATUS.FULFILLED // 更改状态
this._value = value // 储存当前值,用于then回调
}
// +++++ 改变状态后,如果有then的回调还没处理,那么处理它 +++++
while (this._onFulfilledQueue.length) {
const callback = this._onFulfilledQueue.shift()
callback(value)
}
}
// reject 在状态为pending时,将状态改成 rejected
const reject = reason => {
if (this._status === STATUS.PENDING) {
this._status = STATUS.REJECTED
this._reason = reason
}
// +++++ 改变状态后,如果有then的回调还没处理,那么处理它 +++++
while (this._onRejectedQueue.length) {
const callback = this._onRejectedQueue.shift()
callback(reason)
}
}
再测试一下之前失败的例子,打印成功了,说明是有效果的
// 像这样,是不会resolve的
const p = new MyPromise((resolve, reject) => {
setTimeout(() => resolve(1), 1)
})
p.then(console.log, null) // 1
第四步:给promise定型方法resolve和reject加上异步
resolve和reject是在时间循环的末尾执行的,这里我们得加上异步setTimeout实现
// resolve => 在状态为pending时,将状态改成 fulfilled
const resolve = value => {
if (this._status === STATUS.PENDING) {
// +++++ 在事件循环末尾执行 +++++
setTimeout(() => {
this._status = STATUS.FULFILLED // 更改状态
this._value = value // 储存当前值,用于then回调
// 改变状态后,如果有then的回调还没处理,那么处理它
while (this._onFulfilledQueue.length) {
const callback = this._onFulfilledQueue.shift()
callback(value)
}
}, 0)
}
}
// reject 在状态为pending时,将状态改成 rejected
const reject = reason => {
// +++++ 在事件循环末尾执行 +++++
setTimeout(() => {
if (this._status === STATUS.PENDING) {
this._status = STATUS.REJECTED
this._reason = reason
}
// 改变状态后,如果有then的回调还没处理,那么处理它
while (this._onRejectedQueue.length) {
const callback = this._onRejectedQueue.shift()
callback(reason)
}
}, 0)
}
目前,完整代码是这样的:
// 定义promise的三种状态
const STATUS = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected',
}
// 新建MyPromise类,它接受一个执行器函数executor
class MyPromise {
constructor(executor) {
this._status = STATUS.PENDING // promise初始状态为pending
this._value = void 0 // 调用then回调的第一个参数的返回值
this._reason = void 0 // 调用then回调的第二个参数的返回值
this._onFulfilledQueue = [] // resolve时触发的成功队列
this._onRejectedQueue = [] // reject时触发的失败队列
// resolve => 在状态为pending时,将状态改成 fulfilled
const resolve = value => {
if (this._status === STATUS.PENDING) {
setTimeout(() => {
this._status = STATUS.FULFILLED // 更改状态
this._value = value // 储存当前值,用于then回调
// 改变状态后,如果有then的回调还没处理,那么处理它
while (this._onFulfilledQueue.length) {
const callback = this._onFulfilledQueue.shift()
callback(value)
}
}, 0)
}
}
// reject 在状态为pending时,将状态改成 rejected
const reject = reason => {
setTimeout(() => {
if (this._status === STATUS.PENDING) {
this._status = STATUS.REJECTED
this._reason = reason
}
// 改变状态后,如果有then的回调还没处理,那么处理它
while (this._onRejectedQueue.length) {
const callback = this._onRejectedQueue.shift()
callback(reason)
}
}, 0)
}
// 立即执行执行器函数,出错则调用reject捕获错误
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
// 类方法then
then(onFulfilled, onRejected) {
// 对传入的参数进行检查,如果不是回调函数:在成功回调中直接返回,在失败回调中作为错误抛出
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected =
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason
}
// 如果是pending状态则加入队列
if (this._status === STATUS.PENDING) {
this._onFulfilledQueue.push(onFulfilled)
this._onRejectedQueue.push(onRejected)
}
// 根据状态判断调用哪个回调
if (this._status === STATUS.FULFILLED) {
// 成功时调用onFulfilled回调
setTimeout(() => onFulfilled(this._value), 0)
} else if (this._status === STATUS.REJECTED) {
// 失败时调用onRejected回调
setTimeout(() => onRejected(this._reason), 0)
}
}
}
const p = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('马上就要成功了'), 1000)
})
p.then(console.log) // 马上就要成功了
至此,我们现在已经给Promise加如了异步逻辑,但明显还是不够的,接下来我们要让他能够链式调用起来。
5.3 实现then的链式调用
如果要实现链式调用,那么我们就得让它返回一个和这个实例相同类型的实例,就都有了then方法,那不就可以循环调用了?因此我们的then方法也需要返回一个Promise实例。看一下这个ES6中promise链式调用的例子:
const p = new Promise((resolve, reject) => {
resolve(1)
})
p.then(res => {
console.log('then1:', res) // 1
return 2
}).then(res => {
console.log('then2:', res) // 2
})
其实,这个得分情况讨论:
- 如果上一个promise是成功
fulfilled状态,那么可能会调用onFulfilled回调,那么:- 如果
onFulfilled是函数,那么要使用onFulfilled对上一个promise暴露值进行处理,根据其返回值决定下一步,如果返回值是promise那么得调用then方法将它展开,否则直接暴露出去 - 如果
onFulfilled不是函数,要直接暴露value,即resolve(value)
- 如果
- 如果上一个promise是拒绝
rejected状态,那么可能会调用onRejected回调,那么:- 如果
onRejected是函数,那么要使用onRejected进行错误处理,根据其返回值决定下一步,如果返回值是promise那么得调用then方法将它展开,否则直接暴露出去 - 如果
onRejected不是函数,要直接暴露reason,即resolve(reason)
- 如果
- 如果上一个promise是
pending状态,那么和前面一样分情况讨论加入队列即可
还有什么需要注意的呢?执行的成功或者失败的回调可能会产生又一个promise或者thenable类型的对象,那么必须要对其进行展开才行,我们用方法resolvePromise进行判断。最终实现结果如下,这里之前使用的if...else可以改成switch...case相对美观,顺手修改,详细操作见注释
// 定义promise的三种状态
const STATUS = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected',
}
class MyPromise {
constructor(executor) {
this._status = STATUS.PENDING // promise初始状态为pending
this._value = void 0 // 调用then回调的第一个参数的返回值
this._reason = void 0 // 调用then回调的第二个参数的返回值
this._onFulfilledQueue = [] // resolve时触发的成功队列
this._onRejectedQueue = [] // reject时触发的失败队列
// resolve => 在状态为pending时,将状态改成 fulfilled
const resolve = value => {
if (this._status === STATUS.PENDING) {
setTimeout(() => {
this._status = STATUS.FULFILLED // 更改状态
this._value = value // 储存当前值,用于then回调
// 改变状态后,如果有then的回调还没处理,那么处理它
while (this._onFulfilledQueue.length) {
const callback = this._onFulfilledQueue.shift()
callback(value)
}
}, 0)
}
}
// reject 在状态为pending时,将状态改成 rejected
const reject = reason => {
setTimeout(() => {
if (this._status === STATUS.PENDING) {
this._status = STATUS.REJECTED
this._reason = reason
}
// 改变状态后,如果有then的回调还没处理,那么处理它
while (this._onRejectedQueue.length) {
const callback = this._onRejectedQueue.shift()
callback(reason)
}
}, 0)
}
// 立即执行执行器函数,出错则调用reject捕获错误
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
// 类方法then
then(onFulfilled, onRejected) {
// 对传入的参数进行检查,如果不是回调函数:在成功回调中直接返回,在失败回调中作为错误抛出
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected =
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason
}
// 被返回的新的promise
const thenPromise = new MyPromise((resolve, reject) => {
// 根据状态判断调用哪个回调
switch (this._status) {
// 成功时
case STATUS.FULFILLED:
setTimeout(() => {
try {
// +++++调用onFulfilled回调,看返回值做动作+++++
let cbr = onFulfilled(this._value)
resolvePromise(thenPromise, cbr, resolve, reject)
} catch (err) {
// 出错了,暴露错误
reject(err)
}
}, 0)
break
// 失败时调用onRejected回调
case STATUS.REJECTED:
setTimeout(() => {
try {
// +++++调用onRejected回调,看返回值做动作+++++
let cbr = onRejected(this._reason)
resolvePromise(thenPromise, cbr, resolve, reject)
} catch (err) {
// 出错了,暴露错误
reject(err)
}
}, 0)
break
// +++++如果是pending状态则加入队列,也不能落下返回值的讨论+++++
case STATUS.PENDING:
// +++++成功队列+++++
this._onFulfilledQueue.push(() => {
try {
let cbr = onFulfilled(this._value)
resolvePromise(thenPromise, cbr, resolve, reject)
} catch (err) {
reject(err)
}
})
// +++++失败队列+++++
this._onRejectedQueue.push(() => {
try {
let cbr = onRejected(this._reason)
resolvePromise(thenPromise, cbr, resolve, reject)
} catch (err) {
reject(err)
}
})
break
}
})
// 返回一个promise
return thenPromise
}
}
// +++++根据回调返回值处理暴露结果+++++
function resolvePromise(thenPromise, cbr, resolve, reject) {
// 如果回调函数返回了本身就出现了循环引用,自己拒绝并暴露错误TypeError
if (cbr === thenPromise) {
return reject(new TypeError('循环引用了自己!'))
}
// 如果回调函数返回了promise值,则需要将其展开
if (cbr instanceof MyPromise) {
if (cbr._status === STATUS.PENDING) {
// 如果cbr还是pending状态,我们需要等到它真的定型之后拿到它去做判断
// 相当于这里只是做了等待处理
cbr.then(realCBR => {
return resolvePromise(thenPromise, realCBR, resolve, reject)
}, reject)
} else if (cbr._status === STATUS.FULFILLED) {
// 如果cbr处于成功状态,则拿到cbr的成功值并暴露出去
return resolve(cbr._value)
} else if (cbr._status === STATUS.REJECTED) {
// 如果cbr处于拒绝状态,则拿到cbr的拒绝原因并暴露出去
return reject(cbr._reason)
}
}
// 如果回调函数返回了一个对象或者函数
else if (
cbr !== null &&
(typeof cbr === 'object' || typeof cbr === 'function')
) {
// 这里主要是判断是不是thenable并根据情况进行处理,判断方法就是前一篇文章提到的鸭子类型检测
// 通过try看他是不是有then,如果不是就拒绝并暴露错误
let then
try {
then = cbr.then
} catch (e) {
return reject(e)
}
// 如果cbr是thenable类型的对象
if (typeof then === 'function') {
// 这里CBR还是只能转型一次,并且如果重复调用还是只能用第一次的结果
let called = false // 标记调用情况
try {
// 把它当promise用,并作用在cbr上即可
then.call(
cbr,
realCBR => {
if (called) return
called = true
return resolvePromise(thenPromise, realCBR, resolve, reject)
},
realREJ => {
if (called) return
called = true
return reject(realREJ)
}
)
} catch (e) {
// 出现错误判断是不是因为重复调用,如果是直接忽略跳过,如果不是暴露错误
if (called) return
called = true
return reject(e)
}
}
// 如果then不是函数,直接定型为成功状态,并暴露cbr
else {
return resolve(cbr)
}
}
// 如果是其他普通值(不是对象和函数)
else return resolve(cbr)
}
最终完成了promise的then方法
5.4 其他静态方法实现
5.4.1 静态方法resolve
// MyPromise.resolve
static resolve(value) {
if (value instanceof MyPromise) {
return value
}
return new MyPromise(resolve => {
resolve(value)
})
}
5.4.2 静态方法reject
// MyPromise.reject
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
5.4.3 静态方法all
// MyPromise.all
static all(promiseArr) {
let count = 0
const result = []
return new MyPromise((resolve, reject) => {
if (!promiseArr.length) {
return resolve(result)
}
promiseArr.forEach((p, i) => {
MyPromise.resolve(p).then(
value => {
count++
result[i] = value
if (count === promiseArr.length) {
resolve(result)
}
},
err => {
reject(err)
}
)
})
})
}
5.5.4 静态方法race
// MyPromise.race
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
promiseArr.forEach(p => {
MyPromise.resolve(p).then(
value => {
resolve(value)
},
err => {
reject(err)
}
)
})
})
}