深入理解 Promise 的实现原理
Promise 的出现大大简化了代码,解决了 JavaScript 被诟病的回调地狱。本文将简要探究 Promise 的内部原理,并手动实现一个简化的 Promise 。
What & Why
Promise 的出现是为了解决 JavaScript 中的回调地狱问题。由于 JavaScript 是单线程非阻塞的,其非阻塞的就是通过回调来实现的,多层回调嵌套就会造成回调地狱,Promise 就是为了解决这个问题。
Promise 标准规定了三个状态:
pending
: 执行中resolved
:执行成功rejected
:执行失败
Promise 也规定了下面的几个方法:
resolve
,如果执行成功,将通过该方法将状态转换为 resolved;reject
,如果执行失败,则通过该方法置未失败;all
,使用该方法可以等待任务数组中的异步任务全部执行成功后返回成功,只要有一个任务为 rejected,则状态为 rejected;race
,只要任务数组中的任意一个任务的状态被改变,则返回该状态的数据给回调函数;then
,定义在原型上的方法,返回一个 Promise 对象,可以支持链式调用;catch
,指定当发生错误时的回调函数;try
,用来替代 new Promise 的写法。
Promise 只是实现了代码视觉上的同步,其实现的本质依然是异步的,只是借助异步事件回调,通过 then 方法产生链式调用,看起来就是按顺序执行。
实现一个 Promise
首先,定义一个 Promise 构造函数,按照 Promise 的规定,定义状态值。
function Promise (callback) {
// 状态
this.state = "pendding";
this.data = null;
this.onthen = [];
// rejected 时的回调
this.onreject = null;
this.funindex = 0;
// 异步执行初始函数
setTimeout(function () {
callback && callback(this.resolve.bind(this), this.reject.bind(this));
}.bind(this));
return this;
}
上面的代码中定义了 Promise 构造函数,内部定义了状态变量。onthen
是一个数组,用来存放 then 的回调函数。
由 JavaScript 的时间循环机制可知,then 方法处在宏任务队列中,所以会首先依次执行 then 方法。所以我们来定义 then 方法,在 then 方法内部将 回调函数推入 onthen 数组中。
Promise.prototype.then = function (callback) {
this.onthen.push(callback);
return this;
}
同时,resolve 方法需要在内部执行 onthen 中的方法,并继续返回当前 Promise 对象。
Promise.prototype.resolve = function (data) {
this.state = "resolved";
this.data = data;
setTimeout(function () {
for (let i = 0; i < this.onthen.length; i++) {
let then = this.onthen.shift();
this.data = then(this.data);
}
}.bind(this));
return this;
}
reject 方法也需要返回当前的 Promise 对象,同时执行 onreject 函数,用以捕获错误。
// 抛出错误
Promise.prototype.reject = function (error) {
this.state = "rejected";
this.onreject(error);
return this;
}
catch 方法用来捕获错误,当错误被捕获时,Promise 的状态应该被清零。
Promise.prototype.catch = function (callback) {
this.onthen = [];
this.onreject = callback;
}
测试 Promise
测试上面手写的 Promise :
let mypromise = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("hello");
}, 1000);
});
mypromise.then(function (data) {
console.log(data);
return 1;
})
.then(function (data) {
console.log(data);
return data + 1;
})
.then(function (data) {
console.log(data);
return data + 1;
})
.then(function (data) {
console.log(data);
return data + 1;
})
.catch(function (err) {
console.log(err)
})
// 输出
// hello
// 1
// 2
// 3