原文地址: https://www.jeremyjone.com/773/ ,转载请注明
已经写了3篇前置内容了,主要是理解JS中的异步编程,异步的实现、以及异步的原理。今天内容较长,从最简单、最基本的内容入手,一点一点手撸一个简易的 Promise,巩固之前理解的异步原理,这才是我的目标。
了解 Promise,从手动重写一个简易版的开始。
最基本的 Promise 的样子是这样的:
new Promise((resolve, reject) => {});
那么照猫画虎写一个:
class MyPromise {
constructor(executor) {
this.status = "pending";
this.value = null;
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
resolve(val) {
if (this.status === "pending") {
this.status = "fulfilled";
this.value = val;
}
}
reject(err) {
if (this.status === "pending") {
this.status = "rejected";
this.value = err;
}
}
}
这样就得到了一个最基本的样子,来试一下:
let p1 = new MyPromise((resolve, reject) => {}); // 此时为 pending 状态
let p2 = new MyPromise((resolve, reject) => {
resolve();
}); // 此时为 fulfilled 状态
let p3 = new MyPromise((resolve, reject) => {
reject();
}); // 此时为 rejected 状态
好像没什么毛病了。接下来实现 then 的链式操作。
前文已经提到过,它应该也是一个方法,所以我们继续在 MyPromise 类中添加一个 then 方法:
// 继续添加代码,已有代码不再重复
class MyPromise {
constructor(executor) {
// 添加两个回调接收,用于 then 的异步回调
this.cbFulfilled = null;
this.cbRejected = null;
}
then(resolve, reject) {
// 先判断两个参数是否为函数,如果不是或者没有,给一个默认值
if (typeof resolve !== "function") {
resolve = () => {};
}
if (typeof reject !== "function") {
reject = () => {};
}
// 初始状态,异步情况下会是这个状态
if (this.status === "pending") {
this.cbFulfilled = resolve;
this.cbRjected = reject;
}
// 成功状态
if (this.status === "fulfilled") {
setTimeout(() => {
try {
resolve(this.value);
} catch (error) {
reject(error);
}
});
}
// 失败
if (this.status === "rejected") {
setTimeout(() => {
try {
reject(this.value);
} catch (error) {
reject(error);
}
});
}
}
// 修改之前的代码
resolve(val) {
if (this.status === "pending") {
this.status = "fulfilled";
this.value = val;
// 添加回调
setTimeout(() => {
this.cbFulfilled && this.cbFulfilled("timeout " + this.value);
});
}
}
reject(err) {
if (this.status === "pending") {
this.status = "rejected";
this.value = err;
// 添加回调
setTimeout(() => {
this.cbRejected && this.cbRejected("timeout " + this.value);
});
}
}
}
这里我们通过使用 setTimeout
来对执行顺序加以控制,使回调成为一个异步调用。测试一下:
let p1 = new MyPromise((resolve, reject) => {
console.log(1);
setTimeout(() => {
resolve("jeremyjone");
console.log(4);
});
console.log(2);
}).then(val => console.log(val));
console.log(3);
它的打印顺序:
1
2
3
4
jeremyjone
现在看上去已经和原生的效果差不多了。下一步我们让它成为链式的。
要实现链式操作,首先要明确:
既然是要一个 Promise,那么我们首先将 then
里面的方法包装在一个 Promise 中。然后稍微修改一下逻辑就可以实现链式操作了。
class MyPromise {
// ... 其他代码省略
then(resolve, reject) {
// 先判断两个参数是否为函数,如果不是或者没有,给一个默认值
if (typeof resolve !== "function") {
resolve = () => {};
}
if (typeof reject !== "function") {
reject = () => {};
}
// 将改变值的内容包装在一个新的 Promise 中
return new MyPromise((newResolve, newReject) => {
if (this.status === "pending") {
this.cbFulfilled = val => {
try {
// 将当前 then 中的返回值,赋值给下一次的 then,并使其改变状态
let res = resolve(val);
newResolve(res);
} catch (error) {
// 当前 then 的异常,交给下一个 then 去解决,直接调用 reject 回调函数即可
newReject(error);
}
};
this.cbRejected = val => {
try {
let res = reject(val);
// 当前接收的状态,并不影响下一次 then 的状态,所以当前的拒绝状态也返回成功状态给下一次,只有当前的异常才会修改为拒绝
newResolve(res);
} catch (error) {
newReject(error);
}
};
}
if (this.status === "fulfilled") {
setTimeout(() => {
try {
let res = resolve(this.value);
newResolve(res);
} catch (error) {
newReject(error);
}
});
}
if (this.status === "rejected") {
setTimeout(() => {
try {
let res = reject(this.value);
newResolve(res);
} catch (error) {
newReject(error);
}
});
}
});
}
}
上面代码替换完之后,现在 then
每次返回的将是一个 Promise,它已经可以链式操作了。我在代码中的对应行也添加了一些注释,以方便理解。
then
方法时返回,如果外部的 then
方法内部并没有 return 语句,那么则为 undefined
。resolve
。在外面,我们可以通过链式操作测试一下:
new MyPromise((resolve, reject) => {
console.log(1);
setTimeout(() => {
// resolve("jeremyjone");
reject("failed");
console.log(4);
}, 1000);
console.log(2);
})
.then(
val => {
setTimeout(() => {
console.log(val);
}, 1000);
return "p1";
},
err => {
console.log("err1 ", err);
return "p1 err";
}
)
.then(
val => console.log("2 " + val),
err => console.log("2 err ", err)
);
console.log(3);
执行结果:
1
2
3
4
err1 timeout failed
2 timeout p1 err
可以看到,已经可以执行,而且第二个 then
中使用成功的回调方法接收了前一个 then
里面拒绝方法中的返回值。
上面的代码中,不能实现状态穿透,也就是当前 then
不处理结果时,需要向下继续穿透,类似:
new MyPromise((resolve, reject) => {})
.then() // 此处属于状态穿透
.then(
val => console.log(),
err => console.log(err)
);
为了解决这个问题,我们继续稍加修改上面的 then
方法。
class MyPromise {
// ... 其他代码省略
then(resolve, reject) {
// 如果是成功状态,直接返回值,没有值就是空着即可
if (typeof resolve !== "function") {
resolve = () => this.value;
}
// 拒绝的状态需要处理。因为我们之前的逻辑是当前 then 的状态不会影响下一次的。现在需要除了穿透的状态,所以要先判断一下是否为函数属性
// 添加一个判断是否为函数的属性
const isRejectFunc = typeof reject === "function";
if (typeof reject !== "function") {
reject = () => this.value;
}
// 将改变值的内容包装在一个新的 Promise 中
return new MyPromise((newResolve, newReject) => {
if (this.status === "pending") {
this.cbFulfilled = val => {
try {
let res = resolve(val);
newResolve(res);
} catch (error) {
newReject(error);
}
};
this.cbRejected = val => {
try {
// 两个拒绝的地方需要修改,如果是一个函数,走之前的方式。如果不是函数,则使用默认的拒绝方法将值继续向下传递
if (isRejectFunc) {
let res = reject(val);
newResolve(res);
} else {
newReject(val || this.value);
}
} catch (error) {
newReject(error);
}
};
}
if (this.status === "fulfilled") {
setTimeout(() => {
try {
let res = resolve(this.value);
newResolve(res);
} catch (error) {
newReject(error);
}
});
}
if (this.status === "rejected") {
setTimeout(() => {
try {
// 第二个拒绝的地方
if (isRejectFunc) {
let res = reject(this.value);
newResolve(res);
} else {
newReject(this.value);
}
} catch (error) {
newReject(error);
}
});
}
});
}
}
现在就可以实现状态的穿透了。
解决了上面的穿透问题,现在处理返回 Promise 对象的问题。
这个问题其实很好解决,只需要在 then
方法内部判断一下类型,然后如果是 Promise 对象等待其值,不是直接返回结果即可。
直接看修改的代码:
// ... 其他内容不写了
if (this.status === "fulfilled") {
setTimeout(() => {
try {
let res = resolve(this.value);
// newResolve(res);
// 刚才我们直接调用回调函数,现在判断一下再调用
if (res instanceof MyPromise) {
res.then(newResolve, newReject);
} else {
newResolve(res);
}
} catch (error) {
newReject(error);
}
});
}
其内部有 4 处相同内容,全部修改即可。现在返回的是一个 MyPromise 也不会有什么问题。
new MyPromise((resolve, reject) => {
resolve("jeremyjone");
})
.then(
val => {
console.log(val);
return new MyPromise((resolve, reject) => {
resolve("22222");
});
},
err => console.log(err)
)
.then(
val => console.log("2then ok " + val),
err => console.log("2then err " + err)
);
console.log("11111");
执行结果:
11111
jeremyjone
2then ok timeout 22222
其实核心内容已经差不多了,现在写一些静态方法,让我们自定义的 MyPromise 看上去更像 Promise。
注意静态方法不要忘记 static
。
前面已经讲过,它返回一个成功状态,所以很简单,只需要新建一个 Promise 并返回成功即可。
class MyPromise {
static resolve(value) {
return new MyPromise((resolve, reject) => {
if (value instanceof MyPromise) {
// 如果传入的参数本身是一个 Promise,则按照其本身返回的状态返回。
value.then(resolve, reject);
} else {
resolve(value);
}
});
}
}
与上面的同理,只需要修改为拒绝状态即可。
class MyPromise {
static reject(value) {
return new MyPromise((resolve, reject) => {
if (value instanceof MyPromise) {
value.then(resolve, reject);
} else {
reject(value);
}
});
}
}
它稍微有一些复杂,但是并不会比核心的 then
还困难。只需要一个循环,将所有值放在一个数组中,同时遇到错误直接抛出。最后,将结果返回即可。
class MyPromise {
static all(promises) {
const res = [];
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
const promise = promises[i];
if (promise instanceof MyPromise) {
promise.then(
val => {
// 使用 push 会有位置影响
res[i] = val;
// 因为使用赋值,所以可能存在空,需要判空
res.filter(x => !!x).length === promises.length && resolve(res);
},
err => reject(err)
);
} else {
res[i] = promise;
}
}
});
}
}
这个更简单。它返回最快返回的值。一次循环,只要拿到了值就返回即可。
它利用了 Promise 只修改一次状态的特性,这是我们前面写过的。
class MyPromise {
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.map(promise => {
if (promise instanceof MyPromise) {
promise.then(
val => resolve(val),
err => reject(err)
);
} else {
resolve(promise);
}
});
});
}
}
前面已经把大部分代码都按部分总结了,最后整理一下。
class MyPromise {
constructor(executor) {
this.status = "pending";
this.value = null;
this.cbFulfilled = null;
this.cbRejected = null;
try {
executor(this._resolve.bind(this), this._reject.bind(this));
} catch (error) {
this._reject(error);
}
}
_exec(promise, isRejectFunc, res, resolve, reject) {
// 在 promise 中返回自身,抛出错误
if (promise === res) throw Error("Chaining cycle detected for promise");
try {
if (res instanceof MyPromise) {
res.then(resolve, reject);
} else {
isRejectFunc ? resolve(res) : reject(res);
}
} catch (error) {
reject(error);
}
}
_resolve(val) {
if (this.status === "pending") {
this.status = "fulfilled";
this.value = val;
// 添加回调
setTimeout(() => {
this.cbFulfilled && this.cbFulfilled(this.value);
});
}
}
_reject(err) {
if (this.status === "pending") {
this.status = "rejected";
this.value = err;
// 添加回调
setTimeout(() => {
this.cbRejected && this.cbRejected(this.value);
});
}
}
then(resolve, reject) {
// 先判断两个参数是否为函数,如果不是或者没有,给一个默认值
if (typeof resolve !== "function") {
resolve = () => this.value;
}
const isRejectFunc = typeof reject === "function";
if (typeof reject !== "function") {
reject = () => this.value;
}
// 将改变值的内容包装在一个新的 Promise 中
let p = new MyPromise((newResolve, newReject) => {
// 初始状态,异步情况下会是这个状态
if (this.status === "pending") {
this.cbFulfilled = val => {
let res = resolve(val);
this._exec(p, true, res, newResolve, newReject);
};
this.cbRejected = val => {
let res = reject(val);
this._exec(p, isRejectFunc, res, newResolve, newReject);
};
}
// 成功状态
if (this.status === "fulfilled") {
setTimeout(() => {
let res = resolve(this.value);
this._exec(p, true, res, newResolve, newReject);
});
}
// 失败
if (this.status === "rejected") {
setTimeout(() => {
let res = reject(this.value);
this._exec(p, isRejectFunc, res, newResolve, newReject);
});
}
});
return p;
}
catch(reject) {
return this.then(null, reject);
}
static resolve(value) {
return new MyPromise((resolve, reject) => {
if (value instanceof MyPromise) {
// 如果传入的参数本身是一个 Promise,则按照其本身返回的状态返回。
value.then(resolve, reject);
} else {
resolve(value);
}
});
}
static reject(value) {
return new MyPromise((resolve, reject) => {
if (value instanceof MyPromise) {
value.then(resolve, reject);
} else {
reject(value);
}
});
}
static all(promises) {
const res = [];
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
const promise = promises[i];
if (promise instanceof MyPromise) {
promise.then(
val => {
// 使用 push 会有位置影响
res[i] = val;
// 因为使用赋值,所以可能存在空,需要判空
res.filter(x => !!x).length === promises.length && resolve(res);
},
err => reject(err)
);
} else {
res[i] = promise;
}
}
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.map(promise => {
if (promise instanceof MyPromise) {
promise.then(
val => resolve(val),
err => reject(err)
);
} else {
resolve(promise);
}
});
});
}
}
测试用例:
new MyPromise((resolve, reject) => {
console.log(1);
setTimeout(() => {
resolve("jeremyjone");
// reject("failed");
console.log(4);
}, 1000);
console.log(2);
})
.then()
.catch(err => console.log("catch:", err))
.then(
val => {
setTimeout(() => {
console.log(val);
}, 1000);
const p2 = new MyPromise((resolve, reject) => {
resolve("小鹰");
});
console.log("111", val);
return p2;
},
err => {
console.log("err1 ", err);
return "p1 err";
}
)
.then(
val => console.log("2 " + val),
err => console.log("2 err ", err)
);
console.log(3);
MyPromise.resolve("resolve jeremyjone").then(val => console.log(val));
MyPromise.reject("reject jeremyjone").then(null, err => console.log(err));
MyPromise.resolve(
new MyPromise((resolve, reject) => {
reject("reject in resolve");
})
).then(
val => console.log("rr1", val),
err => console.log("rr2", err)
);
let p1 = new MyPromise((resolve, reject) => {
resolve("p1");
});
let p2 = new MyPromise((resolve, reject) => {
resolve("p2");
});
MyPromise.all([p1, p2, 3, 4]).then(
val => console.log("all ok", val),
err => console.log("all err", err)
);
let p3 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("p3");
}, 1000);
});
let p4 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject("p4");
}, 1000);
});
MyPromise.race([p3, p4]).then(
val => console.log("race ok", val),
err => console.log("race err", err)
);
通过本系列文章,你应该掌握了:
当然,这个完整版不能和原生的比较,还有很多细节没有实现。这里只是总结一下其实现的核心,了解并掌握其实现的原理,通过这个实例,掌握异步核心,了解JS异步运行机制,才是我们应该学到的。