有过 Ajax 经验的童鞋一定对“回调地狱”不陌生,为了取得异步请求的结果,我们往往要提供一个回调函数,一个异步请求的情况下并没有什么问题,如果在回调中我们还要进行异步请求,我们只能在回调中再嵌套回调函数,一旦异步次数多了,就变成了回调中嵌套回调、存在很多层结构的回调地狱,那样的代码根本不适合阅读。
ES6 中新增的 Promise 对象是一种新的异步解决方案,它的出现很好的解决了异步回调多层嵌套问题,通过 then 方法将以前的嵌套函数变成了平铺函数,代码一目了然,便于阅读。
但 ES6 的普及率仍然不高,在浏览器标准参差不齐的 Web 端,我们还是尽量少用吧,除非你不打算对低版本浏览器进行支持。
本着对 Promise 实现原理的研究,我们今天就来自己通过原生 JS 来实现一个 Promise 对象。
Promise 定义
所谓 Promise,就是一个单向状态机,里面保存着一个异步或同步动作,而这个状态机的状态会随着异步或同步动作的结果来决定。
Promise 有两个特点:
- 对象的状态不受外界影响。Promise 有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有其内部保存的异步或同步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变 Promise 状态。
- 状态的单向改变。Promise 的状态只能从 Pedding 变成 Resolved / Rejected,无法从 Resolved / Rejected 再变成其他状态;同时任何时候都可以得到这个结果,就算改变已经发生了,你再对 Promise 添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
then方法
在 Promise 标准中,只规定了 Promise 的一个方法 then,其他关于 Promise 的实现提供的其他方法都是基于 then 方法上实现的,所以我们只要实现 then 这一个方法就行了。Promise 的 then 方法是用来注册在这个 Promise 状态确定后的回调,很明显,then 方法需要写在原型链上。现在所有 Promise 的实现在调用 then 方法后都会返回一个新的 Promise,关于这一点,Promise/A+ 标准并没有要求返回的这个Promise 是一个新的对象,但在 Promise/A 标准中,明确规定了 then 要返回一个新的对象,所以在我们的实现中,也让 then 返回一个新的Promise 对象。
构造函数
因为标准并没有指定如何构造一个 Promise 对象,所以我们用和 ES6 原生 Promise 一样的方式去构造 Promise:
function MyPromise(executor){
this.state= "pedding";
this.resolvedCallback = [];
this.rejectedCallback = [];
function resolve(value){
if(this.state === "pedding"){
this.state = "resolved";
this.data = value;
setTimeout(function(){
this.resolvedCallback.forEach(function(fn){
fn && fn(value);
})
}.bind(this), 0);
}
}
function reject(reason){
if(this.state === "pedding"){
this.state = "rejected";
this.data = reason;
setTimeout(function(){
this.rejectedCallback.forEach(function(fn){
fn && fn(reason);
})
}.bind(this), 0);
}
}
try{
executor(resolve.bind(this), reject.bind(this));
} catch(e){
reject(e);
}
}
上面的代码基本实现了 Promise 构造函数的主体,其中有两个注意点:
- 一定要给 executor 函数传两个参数:resolve 和 reject,因为 Promise 中的函数一定会接收到这两个函数。
- 因为 executor 也可能会出错(比如通过 throw 抛出错误),所以如果 executor 出错,这个 Promise 应该也要被 reject。
resolve 和 reject 的实现逻辑基本上就是在判断状态为 pending 之后把状态改为相应的值,并保存对应的 value 和 reason,之后执行相应的回调函数。
then 的实现
then 方法的实现有点复杂,需要考虑的地方比较多,主要就是针对 Promise 当前的状态进行对应的处理,并且对 Promise 的结果的不同也要执行不同的操作。
MyPromise.prototype.then = function(resolvedCallback, rejectedCallback){
var self = this;
var promise;
var x;
// 设置默认操作
resolvedCallback = typeof resolvedCallback === "function" ? resolvedCallback : function(value){return value;};
rejectedCallback = typeof rejectedCallback === "function" ? rejectedCallback : function(reason){return reason;};
// 当前状态为 pedding 时
if(self.state === "pedding"){
// 主要逻辑就是将传入的回调压入 Promise 的回调栈中
return promise = new MyPromise(function(resolve, reject){
self.resolvedCallback.push(function(value){
try {
x = resolvedCallback(value);
// 调用特殊方法进行处理
resolvePromise(promise, x, resolve, reject);
} catch(e){
reject(e);
}
});
self.rejectedCallback.push(function(reason){
try {
x = rejectedCallback(reason);
// 调用特殊方法进行处理
resolvePromise(promise, x, resolve, reject);
} catch(e){
reject(e);
}
});
})
}
// 当前状态为 resolved 时
if(self.state === "resolved"){
// 状态确定,直接执行对应的回调操作
return promise = new MyPromise(function(resolve, reject){
try{
x = resolvedCallback(self.data);
// 调用特殊方法进行处理
resolvePromise(promise, x, resolve, reject);
} catch(e){
reject(e);
}
})
}
// 当前状态为 rejected 时
if(self.state === "rejected"){
// 逻辑同上
return promise = new MyPromise(function(resolve, reject){
try{
x = rejectedCallback(self.data);
// 调用特殊方法进行处理
resolvePromise(promise, x, resolve, reject);
} catch(e){
reject(e);
}
})
}
}
function resolvePromise(promise, x, resolve, reject){
// 当 x 是 MyPromise 或者其他 Promise 的实现时
// 也就是说当 x 有 then 方法时,其就可能是一个 Promise 对象
if (x != null && (typeof x === "object" || typeof x === "function")) {
try {
if (typeof x.then === "function") {
x.then(function(value){
resolvePromise(promise, value, resolve, reject);
}, reject)
} else {
resolve(x);
}
} catch(e) {
reject(e);
}
} else {
resolve(x);
}
}
测试效果
如何对 Promise 的实现进行测试,以判断其是否符合标准?这里有一个配套的测试脚本,只需要我们在一个 CommonJS 的模块中暴露一个 deferred 方法(即exports.deferred方法)。
function MyPromise(executor) {
this.state = "pedding";
this.resolvedCallback = [];
this.rejectedCallback = [];
function resolve(value) {
if (this.state === "pedding") {
this.state = "resolved";
this.data = value;
setTimeout(function () {
this.resolvedCallback.forEach(function (fn) {
fn && fn(value);
})
}.bind(this), 0);
}
}
function reject(reason) {
if (this.state === "pedding") {
this.state = "rejected";
this.data = reason;
setTimeout(function () {
this.rejectedCallback.forEach(function (fn) {
fn && fn(reason);
})
}.bind(this), 0);
}
}
try {
executor(resolve.bind(this), reject.bind(this));
} catch (e) {
reject(e);
}
}
MyPromise.prototype.then = function (resolvedCallback, rejectedCallback) {
var self = this;
var promise;
var x;
// 设置默认操作
resolvedCallback = typeof resolvedCallback === "function" ? resolvedCallback : function (value) {
return value;
};
rejectedCallback = typeof rejectedCallback === "function" ? rejectedCallback : function (reason) {
return reason;
};
// 当前状态为 pedding 时
if (self.state === "pedding") {
// 主要逻辑就是将传入的回调压入 Promise 的回调栈中
return promise = new MyPromise(function (resolve, reject) {
self.resolvedCallback.push(function (value) {
try {
x = resolvedCallback(value);
// 调用特殊方法进行处理
resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.rejectedCallback.push(function (reason) {
try {
x = rejectedCallback(reason);
// 调用特殊方法进行处理
resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
})
}
// 当前状态为 resolved 时
if (self.state === "resolved") {
// 状态确定,直接执行对应的回调操作
return promise = new MyPromise(function (resolve, reject) {
try {
x = resolvedCallback(self.data);
// 调用特殊方法进行处理
resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e);
}
})
}
// 当前状态为 rejected 时
if (self.state === "rejected") {
// 逻辑同上
return promise = new MyPromise(function (resolve, reject) {
try {
x = rejectedCallback(self.data);
// 调用特殊方法进行处理
resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e);
}
})
}
};
function resolvePromise(promise, x, resolve, reject) {
// 当 x 是 MyPromise 或者其他 Promise 的实现时
// 也就是说当 x 有 then 方法时,其就可能是一个 Promise 对象
if (x != null && (typeof x === "object" || typeof x === "function")) {
try {
if (typeof x.then === "function") {
x.then(function (value) {
resolvePromise(promise, value, resolve, reject);
}, reject)
} else {
resolve(x);
}
} catch (e) {
reject(e);
}
} else {
resolve(x);
}
}
Promise.deferred = Promise.defer = function () {
var exp = {};
exp.promise = new MyPromise(function (resolve, reject) {
exp.resolve = resolve;
exp.reject = reject;
});
return exp;
};
module.exports = Promise;
然后执行如下代码即可执行测试:
npm i -g promises-aplus-tests
promises-aplus-tests Promise.js
这里贴上我们的 Promise 实现的结果(仅供娱乐)
最后
写在最后的就是我们实际运用下看看效果。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JS 实现 Promise</title>
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1">
<style>
</style>
</head>
<body>
<script>
function MyPromise(executor) {
this.state = "pedding";
this.resolvedCallback = [];
this.rejectedCallback = [];
function resolve(value) {
if (this.state === "pedding") {
this.state = "resolved";
this.data = value;
setTimeout(function () {
this.resolvedCallback.forEach(function (fn) {
fn && fn(value);
})
}.bind(this), 0);
}
}
function reject(reason) {
if (this.state === "pedding") {
this.state = "rejected";
this.data = reason;
setTimeout(function () {
this.rejectedCallback.forEach(function (fn) {
fn && fn(reason);
})
}.bind(this), 0);
}
}
try {
executor(resolve.bind(this), reject.bind(this));
} catch (e) {
reject(e);
}
}
MyPromise.prototype.then = function (resolvedCallback, rejectedCallback) {
var self = this;
var promise;
var x;
// 设置默认操作
resolvedCallback = typeof resolvedCallback === "function" ? resolvedCallback : function (value) {
return value;
};
rejectedCallback = typeof rejectedCallback === "function" ? rejectedCallback : function (reason) {
return reason;
};
// 当前状态为 pedding 时
if (self.state === "pedding") {
// 主要逻辑就是将传入的回调压入 Promise 的回调栈中
return promise = new MyPromise(function (resolve, reject) {
self.resolvedCallback.push(function (value) {
try {
x = resolvedCallback(value);
// 调用特殊方法进行处理
resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.rejectedCallback.push(function (reason) {
try {
x = rejectedCallback(reason);
// 调用特殊方法进行处理
resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
})
}
// 当前状态为 resolved 时
if (self.state === "resolved") {
// 状态确定,直接执行对应的回调操作
return promise = new MyPromise(function (resolve, reject) {
try {
x = resolvedCallback(self.data);
// 调用特殊方法进行处理
resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e);
}
})
}
// 当前状态为 rejected 时
if (self.state === "rejected") {
// 逻辑同上
return promise = new MyPromise(function (resolve, reject) {
try {
x = rejectedCallback(self.data);
// 调用特殊方法进行处理
resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e);
}
})
}
};
MyPromise.prototype.catch = function(rejectedCallback) {
this.then(null, rejectedCallback);
}
MyPromise.resolve = function(x) {
if (typeof x === "object") {
if (x instanceof MyPromise) {
return x;
}
if (typeof x.then === "function") {
return new MyPromise((resolve, reject) => {
x.then(resolve, reject)
});
}
}
return new MyPromise((resolve, reject) => {
resolve();
})
}
function resolvePromise(promise, x, resolve, reject) {
// 当 x 是 MyPromise 或者其他 Promise 的实现时
// 也就是说当 x 有 then 方法时,其就可能是一个 Promise 对象
if (x != null && (typeof x === "object" || typeof x === "function")) {
try {
if (typeof x.then === "function") {
x.then(function (value) {
resolvePromise(promise, value, resolve, reject);
}, reject)
} else {
resolve(x);
}
} catch (e) {
reject(e);
}
} else {
resolve(x);
}
}
</script>
<script>
var time = Date.now()
var promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("First");
}, 1000)
});
promise.then((value) => {
console.log(value);
console.log(Date.now() - time);
})
promise.then((value) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Second");
}, 1000)
})
}).then((value) => {
console.log(value);
console.log(Date.now() - time);
}, (err) => {
console.log(err);
console.log(Date.now() - time);
})
promise.then((value) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Third");
}, 2000)
})
}).then((value) => {
console.log(value);
console.log(Date.now() - time);
}, (err) => {
console.log(err);
console.log(Date.now() - time);
throw Error("Error");
}).catch((err) => {
console.log(err.toString());
console.log(Date.now() - time);
})
var p1 = MyPromise.resolve("Hello");
p1.then(() => {
console.log("new Promise p1");
}, () => {
console.log("new Promise p1");
})
var p1 = MyPromise.resolve({
then: (resolve, reject) => {
setTimeout(() => {
resolve("new Promise p2");
}, 1000)
}
});
p1.then((value) => {
console.log(value);
}, (err) => {
console.log(err);
})
</script>
</body>
</html>