Promise,作为一个前端工程师,或多或少都避免不了使用他。作为一种异步编程的解决方案,它相较于传统的回调函数,功能更强大,书写更简洁合理。Promise的出现本质就是为了解决回调地狱的问题。
什么是回调地狱
提到回调地狱的问题,大部分人都会用ajax作为例子讲解,这边本人也免不了俗的用一个ajax的例子:
$.ajax({
urL: 'savea',
success: function () {
$.ajax({
url: 'saveb',
success: function () {
$.ajax({
url: 'submit',
success: function () {
}
})
}
})
}
})
从上面的代码我们就能看到问题的所在,当嵌套回调过多的时候代码就会变得又臭又长,旁人过来一眼看去,那真的简直了,给后期的维护也会带来很大的问题,比如某天某个**产品过来说这个改一下,要先保存b,在保存a,那就呵呵了。
Promise的链式调用
Promise的一大特点就是它的链式调用,用Promise改写上面的代如下:
var saveA = new Promise(function (resolve,reject) {
$.ajax({
url:'savea',
success: function (response) {
resolve(response);
//err: reject()
}
})
});
saveA.then(function () {
$.ajax({
url:'saveb',
success: function () {
}
})
}).then(function () {
$.ajax({
url:'submit',
success: function () {
}
})
});
这样看上去结构就显得更加清晰明了,就是初次看代码的人也能一眼看出来前后依赖关系,这种现象在出现多层回调更加明显。
以上就是一个Promise的最简单的使用方式,关于他的其他使用方式本文不做介绍,具体可以参考阮一峰的 ECMAScript 6 入门:Promise 对象
Promise的原理分析
介绍了简单的使用,接下来就是怎么去自己实现Promise了。参考上面的例子我们不难发现,我们使用new命令生成了一个Promise对象,我们传入了一个函数作为参数,这个函数同时有resolve、reject两个函数方法。执行两个函数都会改变Promise对象生成实例的内部状态。它的内部状态图如下:
对象生成的初始状态是pending,resolve执行后状态变更为 fulfilled , reject执行后状态变更为rejected。
初始搭建一个简单的函数
借由上面的思路我们可以初始搭建一个简单的构造函数:
function Promise(executor) {
var status = 'pending';
function resolve(value) {
status = 'fulfill';
console.log(value); //fulfilled
}
function reject(e) {
status = 'reject';
console.log(e); //rejected
}
executor(resolve,reject)
}
var promise = new Promise(function (resolve,reject) {
resolve('fulfilled');
reject('rejected');
});
上面这段代码我们也看出了一点问题,就是在执行完内部状态变了两次,显然这并不是我们想要的,理论上我们应该在状态在pending->fulfilled 或者 pending->rejected之后就不应该再此变更。修改一下上边的代码:
function Promise(executor) {
var status = 'pending';
var done = false;
function resolve(value) {
if(done){
return
}
done = true;
status = 'fulfill';
console.log(value); //fulfilled
}
function reject(e) {
if(done){
return
}
done = true;
status = 'reject';
console.log(e); //不显示
}
executor(resolve,reject)
}
var promise = new Promise(function (resolve,reject) {
resolve('fulfilled');
reject('rejected');
});
上面代码尽管实现了功能,但把逻辑揉杂在一起,不利于维护。我们可以将控制这部分的逻辑剥离出来用一个函数去实现,修改完成的代码如下:
function Promise(executor) {
var status = 'pending';
function resolve(value) {
status = 'fulfill';
}
function reject(e) {
status = 'reject';
}
doOnce(executor,resolve,reject)
}
function doOnce(fn,onFulfill,onReject) {
var done = false;
fn(function (value) {
if(done) return;
done = true;
onFulfill(value)
},function (e) {
if(done) return;
done = true;
onReject(e)
})
}
var promise = new Promise(function (resolve,reject) {
resolve('fulfilled');
reject('rejected');
});
添加then方法
上一步我们实现了Promise的雏形,但关键的链式调用我们还没有实现,这一步,我们开始实现Promise的链式调用。
在实现之前我们回忆一下使用Promise的then方法,简单的:
var promise = new Promise(function (resolve,reject) {
});
promise.then(function () {
//内部状态为fulfilled 执行
},function () {
//内部状态为rejected 执行
})
从上面可以看出来,我们调用then方法时传入了两个函数分别处理内部不同状态,内部状态为fullfilled即执行了resolve函数后,我们调用第一个函数参数;内部状态为rejected即执行了reject函数后调用第二个。同时then返回的是一个执行过resolve或者reject方法的Promise对象,所以接下去才可以继续调用。参考这个模式我们改进一下之前代码:
function Promise(executor) {
var status = 'pending', self = this, value;
function resolve(newValue) {
status = 'fulfill';
value = newValue;
}
function reject(e) {
status = 'reject';
value = e;
}
this.then = function (onFulfilled,onRejected) {
return new self.constructor (function (resolve,reject) {
if(status === 'fulfill') {
resolve(onFulfilled(value));
}else if(status === 'reject') {
reject(onRejected(value));
}
})
};
doOnce(executor,resolve,reject)
}
var promise = new Promise(function (resolve,reject) {
resolve('fulfilled');
});
//then方法调用
promise.then(onResolve,onRejected);
function onResolve(value) {
console.log(value); //fulfilled
}
function onRejected(e) {
console.log(e);
}
那么这里有一个问题来了,如果状态延迟改变呢,即刚开始状态为pending,1s之后状态变了,即:
var promise = new Promise(function (resolve,reject) {
setTimeout(function () {
resolve('fulfilled');
},1000);
});
按照之前的代码的话,没有一个方法会执行。这个时候我们就要对pending状态的做对应的处理,我们想到的应该就是在状态pending时将事件存储起来,等到下个resolve或reject方法执行到的时候一次性执行:
function Promise(executor) {
var status = 'pending', self = this, value = null;
var events = [];
function resolve(newValue) {
status = 'fulfill';
value = newValue;
for(var i = 0,len = events.length; i< len; i++) {
//执行events的所有事件
}
}
function reject(e) {
status = 'reject';
value = e;
}
this.then = function (onFulfilled,onRejected) {
return new self.constructor (function (resolve,reject) {
if(status === 'pending') {
events.push({
onFulfilled: onFulfilled,
onRejected: onRejected,
resolve: resolve,
reject: reject
})
};
if(status === 'fulfill') {
if(onFulfilled === 'null') {
resolve(value);
return;
}
resolve(onFulfilled(value));
}else {
if(onRejected === 'null') {
reject(value);
return
}
reject(onRejected(value));
}
})
};
doOnce(executor,resolve,reject)
}
可以看到then方法里面做了一系列的复杂判断,又出现了之前一样的问题。这个时候我们可以和之前一样把这部分代码抽离出来形成一个独立的方法:
//根据status处理事件
function handle (event) {
if (status === 'pending') {
events.push(event);
return;
}
var cb = (status === 'fulfilled') ? event.onFulfilled : event.onRejected;
if(cb === null) {
(status === 'fulfilled' ? event.resolve : event.reject)(value);
return;
}
var result;
try {
result = cb(value);
} catch (e) {
event.reject(e);
return;
}
event.resolve(result);
}
改进后的代码如下:
function Promise(executor) {
var status = 'pending', self = this, value = null;
var events = [];
function resolve(newValue) {
status = 'fulfill';
value = newValue;
for(var i = 0,len = events.length; i< len; i++) {
handle(events[i]);
events = null;
}
}
function reject(e) {
status = 'reject';
value = e;
}
this.then = function (onFulfilled,onRejected) {
return new self.constructor (function (resolve,reject) {
handle(new HandleCreator(onFulfilled,onRejected,resolve,reject))
})
};
function handle (event) {
if (status === null) {
events.push(event);
return;
}
var cb = status ? event.onFulfilled : event.onRejected;
if(cb === null) {
(status ? event.resolve : event.reject)(value);
return;
}
var result;
try {
result = cb(value);
} catch (e) {
event.reject(e);
return;
}
event.resolve(result);
}
doOnce(executor,resolve,reject)
}
同样的,在reject函数执行时也要执行events里的所有事件,这样我们就可以将这部分抽离出一个函数:
function finale () {
for(var i = 0,len = events.length; i< len; i++) {
handle(events[i]);
events = null;
}
}
到此我们已经完成一个包含then链式调用的Promise函数,完成代码如下:
function Promise(executor) {
if(typeof this !== 'object') throw TypeError('Promise must be new');//promise 必须用new生成
if(typeof executor !== 'function') throw TypeError('param is required function');//参数为函数
var status = 'pending', self = this, value = null;
var events = [];
//将状态更新为fulfilled
function resolve(newValue) {
status ='fulfilled';
value = newValue;
finale();
}
//状态更新为rejected
function reject(e) {
status ='rejected';
value = e;
finale();
}
//then方法返回一个promise
this.then = function (onFulfilled,onRejected) {
return new self.constructor(function (resolve,reject) {
handle(new HandleCreator(onFulfilled,onRejected,resolve,reject));
})
};
//根据status处理事件
function handle (event) {
if (status ==='pending') {
events.push(event);
return;
}
var cb = status === 'fulfilled' ? event.onFulfilled : event.onRejected;
if(cb === null) {
(status === 'fulfilled' ? event.resolve : event.reject)(value);
return;
}
var result;
try {
result = cb(value);
} catch (e) {
event.reject(e);
return;
}
event.resolve(result);
}
//执行所有注册的事件
function finale() {
for(var i = 0, len = events.length; i < len; i++) {
handle(events[i]);
events = null;
}
}
doOnce(executor,resolve,reject);
}
//确保只更改一次状态
function doOnce(fn,onFulfilled,onRejected) {
var done = false;
try {//捕获错误
fn(function (value) {
if(done) return;
done = true;
onFulfilled(value);
},function (e) {
if(done) return;
done = true;
onRejected(e);
})
} catch (e) {
if(done) return;
done = true;
onRejected(e);
}
}
//事件生成器
function HandleCreator(onFulfilled,onRejected,resolve,reject) {
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.resolve = resolve;
this.reject = reject;
}
总结:
之前使用promise的时候,总是一知半解似懂非懂,对于它的原理也没有一个清晰的认知。只有当自己一步步的去调试,实现的过程中才慢慢有了感觉,对Promise才有了一个更深刻的理解。
ps:这里只是实现了then方法,catch方法的实现在后续我会继续去完善,如果各位看到有觉得错误的,希望可以指出,我会及时改正,如果有好的想法也希望可以相互讨论。