第1篇(Ref):搞定 Vue3 Reactivity 响应式源码
一步步,用 Proxy + 发布订阅 模式,手写 vue3/core/reactivity 实现 ref
手撕 Promise,做 2 件事就够了:
then
方法then
方法,其他方法大多是它的衍生其他方法,放到最后,很容易能用 then
衍生出来
new
调用,说明它是一个 class
executor
executor
resolve
、reject
value
和 reason
)pending
、fulfilled
、rejected
pending
pending -> fulfilled
或 pending -> rejected
fulfilled
、rejected
then
方法
then
方法异步接收 resolve/reject
的回调信息then
内的回调函数实际是 微任务
Promise
(pending) 对象(下文称 p
):
onFulfilled
和 onRejected
将被异步执行,即使状态已被敲定p
的行为取决于上一条的执行结果
undefined
作为兑现值(value)Promise
:以该 Promise
作为兑现值Promise
:以该 Promise
作为拒绝值Promise
:保持待定,并在该 Promise
状态改变后以其值作为兑现/拒绝值then
方法const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class Kromise {
constructor(executor) {
this.status = PENDING
// value 和 reason 储存到静态属性里,方便 then 访问
this.value = undefined
this.reason = undefined
// 支持链式调用,以数组保存回调
this.onFulfilledFns = []
this.onRejectedFns = []
// value 传递值
const resolve = (value) => {
// 仅当 pending 时有效
if (this.status === PENDING) {
// 当兑现值是 Promise 时,等待他兑现/拒绝
if (value instanceof Kromise) {
value.then(resolve, reject)
return
}
// Keep simple, 省略 thenable 部分
/**
* 执行回调,微任务:
* 1、确保符合规范
* 2、确保顺序执行 executor 时,不会立即执行 onFulfilled 导致 undefined 错误
*/
queueMicrotask(() => {
// 异步锁,防止异步流程中多次改变状态
if (this.status !== PENDING) return
// 将改变状态代码放到微任务中,为了确保不会过早敲定状态,导致 then 总执行敲定状态后的代码
// 敲定状态
this.status = FULFILLED
// 储存value
this.value = value
this.onFulfilledFns.forEach(fn => fn(this.value))
})
}
}
const reject = (reason) => {
if (this.status === REJECTED) {
queueMicrotask(() => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
this.onRejectedFns.forEach(fn => fn(this.reason))
})
}
}
// 同步执行 executor,并传递参数
executor(resolve, reject)
}
then(onFulfilled, onRejected) {
// 返回 Promise,以支持链式调用
return new Kromise((resolve, reject) => {
// 如果在状态敲定后再执行 then,则立即执行回调
if (this.status === FULFILLED && onFulfilled) {
// 将值传递给返回的 p
const value = onFulfilled(this.value)
resolve(value)
}
if (this.status === REJECTED && onRejected) {
const reason = onRejected(this.reason)
reject(reason)
}
// 暂存,等状态改变(即 resolve/reject 执行)时才真正调用
// try...catch 处理抛出错误的情况
this.onFulfilledFns.push(() => executeFunctionWithErrorCatch(onFulfilled, this.value, resolve, reject))
this.onRejectedFns.push(() => executeFunctionWithErrorCatch(onRejected, this.reason, resolve, reject))
})
}
}
function executeFunctionWithErrorCatch(fn, value, resolve, reject) {
try {
// 将自己的值,传递给返回的 Promise p
const result = execFn(value)
resolve(result)
} catch(err) {
reject(err)
}
}
实际上是 then(undefined, onRejected)
的简写,但这里有一个边界情况要处理:
如何在then未处理onRejected的情况下,将该错误作为返回值传递给接下来链式调用的catch进行处理?
因为 then
仅注册 onFulfilled
回调时,返回的 p
无法将错误传递下去;
解决方法很简单,只需要提供一个默认的 onRejected
实现,保证错误传递即可
catch(onRejected) {
return this.then(undefined, onRejected)
}
// 修改 then 的实现
then(onFulfilled, onRejected) {
// 返回 Promise,以支持链式调用
return new Kromise((resolve, reject) => {
// 如果在状态敲定后再执行 then,则立即执行回调
if (this.status === FULFILLED && onFulfilled) {
// 将值传递给返回的 p
const value = onFulfilled(this.value)
resolve(value)
}
if (this.status === REJECTED && onRejected) {
const reason = onRejected(this.reason)
reject(reason)
}
const defaultOnFulfilled = (value) => value
const defaultOnRejected = (reason) => { throw reason }
onFulfilled = onFulfilled || defaultOnFulfilled
onRejected = onRejected || defaultOnRejected
// 暂存,等状态改变(即 resolve/reject 执行)时才真正调用
// try...catch 处理抛出错误的情况
if (onFulfilled) this.onFulfilledFns.push(() => executeFunctionWithErrorCatch(onFulfilled, this.value, resolve, reject))
if (onRejected) this.onRejectedFns.push(() => executeFunctionWithErrorCatch(onRejected, this.reason, resolve, reject))
})
}
MDN:
Promise
实例的finally()
方法用于注册一个在 promise 敲定(兑现或拒绝)时调用的函数。它会立即返回一个等效的Promise对象
,这可以允许我们链式调用其他 promise 方法
finally(onFinally) {
this.then(onFinally, onFinally)
}