成果
先看结果: 通过了 Promise A+ 测试
执行逻辑与V8一致。
下面道题与V8的
Promise 输出一致 0 1 2 3 4 5 6。
MyPromise.resolve()
.then(() => {
console.log(0);
return MyPromise.resolve(4);
})
.then((res) => console.log(res))
MyPromise.resolve()
.then(() => console.log(1))
.then(() => console.log(2))
.then(() => console.log(3))
.then(() => console.log(5))
.then(() => console.log(6))
原理
先谈一下我认为的promise原理,有错误欢迎支出。
- 基于 订阅发布。
- 订阅:
then进行订阅。then(onFulfilled, onRejected)
- 发布:
resolve、reject发布。new Promise(resolve => resolve(1))
- 订阅:
- 异步。
- 微任务(Microtask)
- 我们可通过
queueMicrotask,创建一个微任务
- 我们可通过
- 微任务(Microtask)
- 链式调用。
- 状态确认后不可变。
实现
下面通过es6的 class,私有属性,箭头函数、queueMicrotask、来实现MyPromise。
逻辑参考了V8源码实现(在下面的参考资料中有读源码文章)。
代码很简单,直接看吧。
class MyPromise {
status = Pending // 初始 Pending状态
reaction = [] // then的订阅队列
value = undefined
constructor(executor) {
// callOnes 保证resolve, reject其中一个函数执行后,再次执行无效
const [resolve, reject] = callOnes(this.#resolve, this.#reject)
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
#resolve = (v) => {
// resolve传入的值,要分多种情况处理
this.#resolvePromise(v)
}
// Fulfille状态确认函数
#fulfil = (v) => {
if (this.status === Pending) {
this.status = Fulfilled
this.value = v
this.#triggerPromiseReactions()
}
}
// Rejected状态确认函数
#reject = (reason) => {
if (this.status === Pending) {
this.status = Rejected
this.value = reason
this.#triggerPromiseReactions()
}
}
// 处理 resolve中result的多种情况
#resolvePromise = (result) => {
// result是this
if (this === result) {
return this.#reject(new TypeError('The promise and the return value are the same'))
}
// 非object直接赋值.
// 注意:function算object, null不算
if (!isObject(result)) {
return this.#fulfil(result);
}
let then;
try {
// 取 then
then = result.then;
} catch (error) {
// 取 then 时抛出错误
return this.#reject(error);
}
// 普通对象,非thenable对象
if (typeof then !== 'function') {
return this.#fulfil(result)
}
// result是myPromise或thenable
// ecma规定,统一在下一个微任务处理
queueMicrotask(() => {
if (then === this.then) {
// result 是传入的值,确认是MyPromise实例。
// 确认this的状态和值,通过 result。
// 为什么then? 因为result可能在Pending状态。
result.then(this.#fulfil, this.#reject)
// 注意微任务执行then,then又会触发微任务,这里会有2个微任务的执行时间。
} else {
// 是thenable(即带有"then"方法的对象)
const [resolve, reject] = callOnes(this.#resolve, this.#reject)
try {
// 执行,this指向thenable
then.call(result, resolve, reject)
} catch (error) {
reject(error)
}
}
})
}
// 订阅onFulfilled, onRejected
then(onFulfilled, onRejected) {
// 返回新promise
const promise = new MyPromise(() => {})
// 保存订阅onFulfilled和onRejected的订阅对象
this.reaction.push({
onFulfilled,
onRejected,
promise, // 保存返回的promise,用于链式调用
})
// 状态已确认,开始发布
if (this.status !== Pending) {
this.#triggerPromiseReactions()
}
return promise
}
// 发布:将then的订阅全部执行,且确认then返回的promise的状态。
#triggerPromiseReactions = () => {
const reaction = this.reaction
this.reaction = []
const handlerKeyMap = {
[Fulfilled]: 'onFulfilled',
[Rejected]: 'onRejected',
}
const handlerKey = handlerKeyMap[this.status]
// 发布:将then的订阅全部执行,且确认then返回的promise的状态。
for (let {[handlerKey]: handler, promise} of reaction) {
// handler:根据status,取出对应订阅函数
// promise:then方法创建且返回的
// 微任务,处理then方法的订阅
queueMicrotask(() => {
if (typeof handler !== 'function') {
// then没有订阅函数。 eg: then(null,null)
// 确认promise的状态和值,通过 this的状态和值,
switch (this.status) {
case Fulfilled:
return promise.#resolve(this.value)
case Rejected:
return promise.#reject(this.value)
}
} else {
// then有订阅函数。eg: then(()=>1))
// 确认promise的状态和值,通过 执行订阅函数
try {
let result = handler(this.value)
return promise.#resolve(result)
} catch (e) {
return promise.#reject(e)
}
}
})
}
}
static resolve(v) {
// ecma规定,Promise.resolve传入promise,直接返回.
if (v instanceof MyPromise) { return v }
return new MyPromise((resolve) => resolve(v))
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason))
}
}
const Pending = 'Pending'
const Fulfilled = 'Fulfilled'
const Rejected = 'Rejected'
// 只能执行一个
function callOnes(a, b) {
let canCalled = true
function warp(fn) {
return (...args) => {
if (canCalled) {
fn(...args)
}
canCalled = false
}
}
return [warp(a), warp(b)]
}
// 判断object
const isObject = v => v !== null && typeof v === 'object' || typeof v === 'function'
为什么没有 catch 等其它方法?
因为Promise A+ 只规定有then方法。 而且其余方法都是包装了then方法。 举例:catch
class MyPromise {
catch(onReject) {
return this.then(null, onReject)
}
}
Promise A+ 测试
github地址,github.com/promises-ap…
配置package.json
{
"name": "promise",
"version": "1.0.0",
"description": "",
"main": "index.js",
"devDependencies": {
"promises-aplus-tests": "*"
},
"scripts": {
"test": "promises-aplus-tests src/promise.js"
},
"keywords": [],
"author": "",
"license": "ISC"
}
开始测试
npm run test
完美通过
执行时间是否与V8相同
找了几道题来测试下。
你可以将MyPromise替换为Promise,输出是一致的。
MyPromise.resolve()
.then(() => {
console.log(0);
throw Promise.resolve(4);
})
.catch((res) => console.log(res))
.then(() => console.log(7))
MyPromise.resolve()
.then(() => console.log(1))
.then(() => console.log(2))
.then(() => console.log(3))
.then(() => console.log(5))
.then(() => console.log(6))
//输出:0 1 2 3 4 5 6
let p1 = MyPromise.resolve()
.then(v => console.log(1))
.then(v => console.log(2))
.then(v => console.log(3))
p1.then(v => console.log(4))
p1.then(v => console.log(5))
let p2 = MyPromise.resolve()
.then(v => console.log(11))
.then(v => console.log(22))
.then(v => console.log(33))
p2.then(v => console.log(44))
p2.then(v => console.log(55))
//输出:1 11 2 22 3 33 4 5 44 55
总结
我们的MyPromise完成了以下要求:
- 通过了 Promise A+ 测试。
- 微任务执行时机 与V8 一致。
代码放到github上了,地址 github.com/liu-zhi-fei…。