哎嘿,是不是被这个标题骗进来了,先别急着关了,我可以狡辩。
为什么说我写的这个牛x呢,我有以下几点理由:
- 作为一个官方已有的实现,我们自己手写的时候,应该尽量贴近官方实现,这点在下面的实现中有一定体现;
- 这次的实现比以前写的都好,所以我觉得他很优秀;
- 求你了,看看吧!!!
起因
为啥突然又想起了手写Promise呢,还是一个阳光明媚的下午,摸鱼翻mdn文档,翻到了Promise这一章,突然发现了一些没见过的名词,什么Promise A+,PromiseLike,thenable。
遇到不懂的东西嘞,那肯定是第一时间问AI呀:
于是乎,又重新了解了一下什么是Promise。像是打开了一扇新的大门啊有木有。
这段话呢就是介绍什么才是一个Pormise,我给上面这段话翻译总结一下就是:
- 一个 promise 是一个对象,并且有一个 then 方法,这个 then 方法可以成功或者失败之后的任务。
- promise 对象还需要有三个状态,两个分支:
- 三个状态分别是 fulfilled, rejected, and pending
- 两个分支需要处理成功和失败后的任务。
- 当Promise状态发生改变的时候,就会一直锁定该状态
看了官方的规范,再想一下曾经面试时候自己手写的Promise,这啥呀这是。
要不重新实现一个呢?
开始实现
时间充裕了,当然是先拆解一下整个的实现。
第一步,实现Promise里面的状态
这里有两个关键点:
- 有三个状态
- 状态会改变,一旦改变就不会再改变
// 将状态提成公共变量方便使用
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class MyPromise {
#value = null;
#state = PENDING;
constructor(callback) {
// 结合平时使用,promise里面传递的函数立即执行
try {
callback(this.#resolve.bind(this), this.#reject.bind(this));
} catch (error) {
this.#reject(error);
}
}
#changeState(state, value) {
if (this.#state !== PENDING) {
return;
}
this.#state = state;
this.#value = value;
}
#resolve(data) {
this.#changeState(FULFILLED, data);
}
#reject(reason) {
this.#changeState(REJECTED, reason);
}
}
第二步,实现then方法
这里的关键点就有点多了
- then方法可以有两个参数,一个执行成功的,一个执行失败的
- then方法的两个回调函数不是立即执行的,要放在微任务队列里面
- then方法有返回值是一个Promise
- 假设有一个
p = new Promise,那么可以多次调用p.then,甚至可以在一个setTimeout之后再调用p.then,所以,当开始执行then函数回调的时候,应该有一个队列,保证每一个p.then都执行了,执行队列的时候,同一个回调函数只会执行一次 - 当执行队列的时候也要分各种情况,我们在代码中详细解释
// 要加入微任务队列,可以考虑封装一个函数来执行, 这几种是常见的添加微队列的方式
const createMicrotask = (callback) => {
if (queueMicrotask) {
queueMicrotask(callback);
return;
}
if (process && process.nextTick) {
process.nextTick(callback);
return;
}
if (MutationObserver) {
if (!observer) {
observer = new MutationObserver(callback);
observer.observe(pDom, { childList: true });
}
pDom.textContent = "1";
return;
}
setTimeout(callback, 0);
};
// then的回调可以是一个Promise,会判断一个对象是不是Promise,也抽离一个方法
const isPromise = (val) => {
// 判断是否符合promiseA+规范
return !!(val && typeof val === "object" && typeof val.then === "function");
};
class MyPromise {
// ... 第一步的代码,继续往后补充then的实现
#handlers = []; // 把handler做成一个对象数组,对象里面存放回调函数,状态,then里面返回的resolve和reject
/**
* 处理异步操作最终完成的结果
* @param {*} onFulfilled 成功回调
* @param {*} onRejected 失败回调
* @returns 返回一个promise
*/
then(onFulfilled, onRejected) {
// 1. 不能直接调用,要等待state状态变了之后才可以执行
// 2. 只能先添加到队列中,等状态变了再执行
// this.#pushHandler(onFulfilled, FULFILLED)
// this.#pushHandler(onRejected, REJECTED)
// 怎么执行返回的promise的resolve和reject呢?
// 3. 一起存起来呗,等执行任务队列的时候,就一起执行了
return new MyPromise((resolve, reject) => {
this.#pushHandler(onFulfilled, FULFILLED, resolve, reject);
this.#pushHandler(onRejected, REJECTED, resolve, reject);
this.#flashHandlers();
});
}
/**
* 向任务队列中添加一个任务,要区分任务是成功的还是失败的
* @param {*} handler
* @param {*} state
* @param {*} resolve
* @param {*} reject
*/
#pushHandler(handler, state, resolve, reject) {
this.#handlers.push({
excutor: handler,
state: state,
resolve: resolve,
reject: reject,
});
}
/**
* 冲刷任务队列,这里每执行一个就丢一个,就不会再次执行了
*/
#flashHandlers() {
if (this.#state === PENDING) return;
while (this.#handlers.length) {
const handler = this.#handlers.shift();
this.#runHandler(handler);
}
}
/**
* 重点来了,执行任务队列中的任务,要处理几个点
* 1. 第一个重点当然是要添加微队列了。。敲黑板!!!
* 2. 如果不是函数,直接将当前的状态,传递给下一个then,记得吧,我们可以`p.then().then()`这样执行,如果第一个then没有回调,那么promise的结果就会透传到第二个里面
* 3. 如果是一个函数,那么就执行这个函数,如果函数有返回值,我们也要分类处理
* - 如果是一个普通的值,那么就直接resolve就行了;
* - 如果返回值是一个Promsie,测试用例
* const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello')
}, 1000)
})
p.then((res) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello1')
}, 1000)
})
})
.then((res) => {
console.log(res + 'world')
})
- 当然,函数执行可能会有错误,如果有错误,就reject了
*/
#runHandler({ excutor, state, resolve, reject }) {
createMicrotask(() => {
if (typeof excutor !== "function") {
// 不是个函数,将当前的状态,传递给下一个then
state === FULFILLED ? resolve(this.#value) : reject(this.#value);
return;
}
try {
if (this.#state === state) {
const result = excutor(this.#value);
// 如果返回的是promise,那么就等待其完成,并继续向下传递
if (isPromise(result)) {
result.then(resolve, reject);
} else {
resolve(result);
}
}
} catch (error) {
reject(error);
}
});
}
}
好,写到这,我们就可以骄傲的说,我们的Promise已经完成了,那还有其他方法呢。
第三步,实现其他静态方法
这里我们先别急,有一个偷懒的方法,那就是打开mdn,搜索Promise,我们就以Promise.resolve()为例
看到这段话了吗,是不是有点懵,哪个看了mdn的不知道这玩意啊。
但是,容我给你翻译一下:
Promise.prototype.resolve = (data) => {
if data is Promise
return data;
return Promise((res, rej) => {
else if data is thenable
return data.then()
else
return data
})
}
这下子有没有更清晰一点呢,按照这个思路,我们就来挨个实现每一个方法,上面我们用的是ES6的类,下面我就接着之前的写了,如果有面试问到只写静态方法的,根据上面伪代码来写就好了。
class MyPromise {
// ... 之前实现的then方法
catch(onRejected) {
return this.then(null, onRejected);
}
/**
* 无论成功还是失败都会执行的逻辑
* @param {*} onSettled
*/
finally(onSettled) {
return this.then(
(data) => {
typeof onSettled === "function" && onSettled();
return data;
},
(reason) => {
typeof onSettled === "function" && onSettled();
return reason;
}
);
}
/**
* Promise.resolve(data)
* @param {*} data
*/
static resolve(data) {
if (data instanceof MyPromise) {
return data;
}
return new MyPromise((resolve, reject) => {
if (isPromise(data)) {
data.then(resolve, reject);
} else {
resolve(data);
}
});
}
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
/**
* 需要返回一个promise
* promise中执行所有的promiseList
* 如果全部resolve,则返回resolve
* 有一个rejected,则返回rejected
* 得到的结果有顺序
* @param {Iterable} promiseList
*/
static all(promiseList) {
return new MyPromise((resolve, reject) => {
const result = [];
let count = 0;
let resultCount = 0;
for (const p of promiseList) {
let index = count;
count++;
// 参数列表中可能有不是promise的,与其判断处理,不如全部包含在一个MyPromise.resolve中处理
MyPromise.resolve(p).then(
(res) => {
result[index] = res;
resultCount++;
if (resultCount === count) {
resolve(result);
}
},
(error) => {
reject(error);
}
);
}
});
}
static allSettled(promises) {
return new MyPromise((resolve, reject) => {
const result = [];
const count = promises.length;
let resultCount = 0;
for (const p of promiseList) {
let index = count;
count++;
// 参数列表中可能有不是promise的,与其判断处理,不如全部包含在一个MyPromise.resolve中处理
MyPromise.resolve(p).then(
(res) => {
result[index] = res;
resultCount++;
if (resultCount === count) {
resolve(result);
}
},
(error) => {
result[index] = error;
resultCount++;
if (resultCount === count) {
resolve(result);
}
}
);
}
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
for (const p of promises) {
MyPromise.resolve(p).then(resolve, reject);
}
});
}
}
结语
以上呢,我们根据官方的规范和文档,实现了一个比较完整的Promise。如果还有不明白的地方,可以留言讨论。也欢迎各位大佬指出问题。
然后,刚开始写文章,希望大佬们给点建议,最近写的比较多的,可能就是一些面试可能遇到的问题。确实现在摸鱼比较多,趁摸鱼时间总结一点。(如果工资高一点也多承担点活?哈哈哈。)
最后就是,如果真的面试遇到了,也不要心急。比如遇到了实现Promise。首先给一个心理预期,比如看了这篇文章之后想好怎么实现了then方法就好了。然后好好想一下怎么拆解这个问题,一步一步的去实现它。
好,啰里吧嗦了一大堆,希望看到这里的同学可以有一点点收获。