其实Promise
自己并不具有异步
的能力,而之因此这里须要单独开一篇说明其原理,是由于Promise
在异步编程
的过程当中是一个不可或缺的一环。缘由下面细说。javascript
在说promise以前,有必要先说下JS中的回调方式。好比下面:java
function doSomethingAfterTime(time, something) {
setTimeout(fun, time);
}
复制代码
可是这样的回调方式有一个问题,可读性太差。另外当回调的层次多了之后,容易陷入回调地狱。举个例子:面试
function func1(cb){
// do something
cb();
}
function func2(cb){
// do something
cb();
}
function func3(cb){
// do something
cb();
}
// do
func1(function(){
func2(function(){
func3(function(){
});
});
});
复制代码
这样的代码读起来简直就是折磨,晕死!编程
下面试着改进下代码,试着将回调函数封装起来。顺便剖析下promise的原理。数组
先来一个最简单的。promise
function Promise(something){
var callback = null;
this.then = function(onCompelete){
callback = onCompelete;
};
something(function (value){
callback(value);
});
}
复制代码
下面是调用代码。bash
// 事件1
function func1(){
return new Promise(function(resolve){
// do something
setTimeout(function(){
console.log('func1');
resolve();
},1000);
});
}
func1().then(function(){
console.log('func1 complete');
});
复制代码
上面对Promise
的封装算是最简单的版本,只是模拟了Promise
的调用方法,好比then
,还有Promise的构造函数
。可是这样的封装没法实现链式调用,链式调用
的核心就是当调用某个方法的时候返回该对象自己
或者该对象对应class的全新对象
。而对于Promise
的改造也很简单.架构
then
方法返回Promise
自己Promoise
须要支持多个callback
。function Promise(something){
var callbacks = [];
this.then = function(onCompelete){
callbacks.push(onCompelete);
return this;
};
function resolve(value){
callbacks.forEach(function(cb){
cb(value);
});
}
something(resolve);
}
复制代码
调用代码以下:异步
func1().then(function(){
console.log('func1 complete');
}).then(function(){
console.log('then2');
}).then(function(){
console.log('then3');
});
复制代码
如今的Promise
执行上面的代码后可以获得正确的执行结果,可是有一个问题,若是咱们想在then
方法再调用一个返回promise
的方法?好比这样:异步编程
// 事件2
function func2(){
return new Promise(function(resolve){
// do something
setTimeout(function(){
console.log('func2');
resolve();
},1000);
});
}
func1().then(func2).then(function(){
console.log('all complete');
});
复制代码
输出以下:
func1
all complete
func2
复制代码
你会发现虽然func2
成功调用了,可是输出顺序乱了,咱们指望的正确输出顺序应该是:
func1
func2
all complete
复制代码
分析下问题出在哪里?问题就出在Promise
中的callbacks
,第一个then
是在func1
返回的Promise
上调用的,而第二个then
事实上仍是在func1
返回的Promise
上调用的。然而咱们但愿的是,第二个then
应该是在func2
返回的Promise
调用,这时候就须要考虑如何进一步改造Promise
了。
对于then
传入的onCompelete
函数参数,它是不知道这个函数具体是否会返回Promise
,只有调用了onCompelete
方法才能知道具体返回的数据。可是onCompelete
是回调函数,你没法直接在then
中调用。所以须要考虑其余的方式。
若是then
方法里面返回一个新的Promise
对象呢?用这个新的Promise
做为中间代理,好比这样:
function Promise(something){
var callbacks = [];
this.then = function(onCompelete){
return new Promise(function (resolve) {
callbacks.push({
onCompelete: onCompelete,
resolve: resolve
});
});
};
function resolve(value){
callbacks.forEach(function(cb){
var ret = cb.onCompelete(value);
cb.resolve(ret);
})
}
something(resolve);
}
复制代码
可是运行的时候你会发现输出顺序仍是没变,仍是有问题的。那么继续分析问题出在哪里? 经过调试发现,resolve
传入的value
有多是promise
对象,而咱们已经在then
方法里面返回了新的promise对象了
,交由该对象做为代理了。所以resolve
传入的value
若是是promise
对象的话,那么就须要把当前promise
的resolve
处理权交出去,交给传入的promise
对象。至关于代理人
把权力交还给实际应该处理的对象。可能有点绕,我再详细的描述下
func1
返回的promise
为p1
,then
返回的promise
为p2
,resolve
传入的promise
对象为p3
,func2
返回的promise
对象为p4
。
上面一共提到4个
promise
对象。
说下描说下调用顺序。
首先由func1
建立p1
,而后调用then
方法建立了p2
,而后再次调用了then
方法,由p2
建立了p3
。p2
和p3
都是由then
建立的代理人。
这时候func1中的异步代码执行了,1秒事后由func1
调用了p1
的resolve
方法,而且将callbacks
数组内的方法依次调用,而后由cb.onCompelete(value)
方法间接获得func2
返回的p4
,接着调用p2
的resolve
方法将p4
传入。可是上面说了,p2
只是个代理,应该把权力
交还给p4
来执行。这样p4
获得权力--回调函数
,当func2
的异步代码执行完毕后,由p4
来执行回调函数。
所以resolve
方法须要进行以下改造。
function resolve(value) {
// 交还权力,而且把resolve传过去
if (value && (typeof value.then === 'function')) {
value.then.call(value, resolve);
return;
}
callbacks.forEach(function (cb) {
var ret = cb.onCompelete(value);
cb.resolve(ret);
});
}
复制代码
上面的代码就是交权
的代码。这样彻底的Promise
修改以下:
function Promise(something) {
var callbacks = [];
this.then = function (onCompelete) {
return new Promise(function (resolve) {
callbacks.push({
onCompelete: onCompelete,
resolve: resolve
});
});
};
function resolve(value) {
if (value && (typeof value.then === 'function')) {
value.then.call(value, resolve);
return;
}
callbacks.forEach(function (cb) {
var ret = cb.onCompelete(value);
cb.resolve(ret);
});
}
something(resolve);
}
复制代码
这样修改事后,再执行以下代码:
func1().then(func2).then(function () {
console.log('all complete');
});
复制代码
如今就能获得正确的执行结果了。
至此,一个简单的Promise
定义完了。这时候有一个问题,若是调用then
方法以前resolve
已经被执行了怎么办呢,岂不是永远都得不到回调了?好比这样:
(new Promise(function (resolve) {
resolve();
})).then(function(){
console.log('complete');
});
复制代码
你会发现then
里面的回调就不会执行了。其实这时候只须要作一个小小的改动就好了。改造以下:
function Promise(something) {
var callbacks = [];
this.then = function (onCompelete) {
return new Promise(function (resolve) {
callbacks.push({
onCompelete: onCompelete,
resolve: resolve
});
});
};
function resolve(value) {
if (value && (typeof value.then === 'function')) {
value.then.call(value, resolve);
return;
}
setTimeout(function(){
callbacks.forEach(function (cb) {
var ret = cb.onCompelete(value);
cb.resolve(ret);
});
},0);
}
something(resolve);
}
复制代码
你会发现,这里只是在resolve
方法里面,将执行的回调放入setTimeout
中,而且timeout
设为0
。这里稍微说下原理
在第一篇中提到
setTimeout
相似定时器
,JS内容在执行setTimeout
的回调函数的时候使用线程调度
的方式将回调函数调度到JS线程
执行。但凡涉及到线程调度
那么确定须要等待JS线程空闲的时候才能调度过来。这时候将timeout设为0,至关于改变了代码执行顺序。
在实际的开发过程当中,上面的Promise
代码仍是缺乏了一个功能,那就是状态管理
,好比:pending
、fulfilled
、rejected
。下面的代码继续加入状态管理
的代码,先添加pending
和fulfilled
的状态:
function Promise(something) {
var callbacks = [];
var state = 0;//0:pending,1:fulfilled
var resultValue = null;
this.then = function (onCompelete) {
return new Promise(function (resolve) {
handleCallBack({
onCompelete: onCompelete,
resolve: resolve
});
});
};
function handleCallBack(callback){
switch(state){
case 0:{
callbacks.push(callback);
break;
}
case 1:{
var ret = callback.onCompelete(resultValue);
callback.resolve(ret);
break;
}
default:{
break;
}
}
}
function resolve(value) {
if (value && (typeof value.then === 'function')) {
value.then.call(value, resolve);
return;
}
state = 1;
resultValue = value;
setTimeout(function(){
callbacks.forEach(function (cb) {
handleCallBack(cb);
});
},0);
}
something(resolve);
}
复制代码
下面再继续加入reject
功能。
function Promise(something) {
var callbacks = [];
var state = 0;//0:pending,1:fulfilled 2:reject
var resultValue = null;
this.then = function (onCompelete, onReject) {
return new Promise(function (resolve) {
handleCallBack({
onCompelete: onCompelete,
resolve: resolve,
reject: onReject
});
});
};
function handleCallBack(callback) {
switch (state) {
case 0: {
callbacks.push(callback);
break;
}
case 1: {
var ret = callback.onCompelete(resultValue);
callback.resolve(ret);
break;
}
case 2: {
if(callback.reject){
var ret = callback.reject(resultValue);
}
callback.resolve(ret);
break;
}
default: {
break;
}
}
}
function reject(error) {
state = 2;
resultValue = error;
setTimeout(function () {
callbacks.forEach(function (cb) {
handleCallBack(cb);
});
}, 0);
}
function resolve(value) {
if (value && (typeof value.then === 'function')) {
value.then.call(value, resolve);
return;
}
state = 1;
resultValue = value;
setTimeout(function () {
callbacks.forEach(function (cb) {
handleCallBack(cb);
});
}, 0);
}
something(resolve,reject);
}
复制代码
OK,经过上面一步一步对Promise
进行修改,基本上是把Promise
的功能完善了。
从这个上面一步一步剖析Promise
原理的过程当中,咱们发现,Promise
自己并不提供异步
功能,Promise
只是对函数的回调功能进行了封装,甚至能够理解为Promise
就是一个回调代理。可是正是有了这个回调代理,使得咱们的回调方式发生了完全的改变,甚至直接影响了项目的架构设计。而在平时的开发过程当中,Promise
在异步编程中起到了几乎不可替代的做用。