原生JS实现Promise
ES6中Promise可以说很大情况下改善了异步回调的嵌套问题,那么如果我们自己去写一个类似Promise的库应该怎么去写?
我们先看一下Promise的特点:
第一:Promise构造函数接受一个函数作为参数,函数里面有两个参数resolve和reject分别作为执行成功或者执行失败的函数
var promise=new Promsie(function(resolve,rejec){
if(/*异步执行成功*/){
resolve(value);
}else{
reject(error);
}
})
第二:可以通过then设置操作成功之后的操作,接受两个函数作为参数
.then(function(){
//回调执行成功之后的操作
},function(){
//回调执行失败之后的操作,可以没有
});
那么原生js实现以上特点应该比较简单了
function PromiseM(){
this.status='pending';
this.msg='';
var process=arguments[0];
var that=this;
process(function(){
that.status='resolve';
that.msg=arguments[0];
},function(){
that.status='reject';
that.msg=arguments[0];
});
return this;
}
PromiseM.prototype.then=function(){
if(this.status=='resolve'){
arguments[0](this.msg);
}
if(this.status=='reject'&&arguments[1]){
arguments[1](this.msg);
}
}
//测试用例
var mm=new PromiseM(function(resolve,reject){
resolve('123');//123其实就是第二个arguments[0]
});//上面的第一个arguments[0]
mm.then(function(success){
console.log(success);//该success其实就是上面的this.msg
console.log("ok!");
},function(){
console.log('fail!');
});
//123
//ok
以上只是最基本的实现,在代码结构结构和容错方面没有进行考虑。
对ES6里promise对象以及异步机制的理解
我现在声明一个a,并且要延时1秒钟后给他赋值为10,然后打印这个a:
var a;
setTimeout(function (){
a = 10;
},1000);//1000ms后给a赋值为10
console.log(a)//undefined,
很显然,a是undefined,因为js是非阻塞的,它不会等你定时器里的东西走完了,再执行打印a这个操作。它不等你读完,就会迫不及待地去打印a,因为a要1秒后才能被赋值,所以此时a只是个undefined。
那怎么办?很简单,把打印a这个操作也放进延时定时器里嘛,我想这个例子并不难理解,那么接下来我会试图将其功能进行拓展,打印a这个需求我并不需要,我想要用a做其他事情,比如让他乘以2并输出返回结果,或者其他的涉及到a的操作。那很简单,我将console.log(a)这段代码删去,封装成一个函数,并将其作为参数传进这个定时器里,也就是所谓的“回调函数”如下:
var a;
function foo(callBack){
setTimeout(function (){
a = 10;
callBack&&callBack()
},1000)
}
foo(function (){ //因为a是全局的,所以不需要将a传参进foo的回调中
return a*2
})
调用foo的时候,作为参数的这个匿名函数,就是所谓的回调函数,回调函数这个机制极大地增加了代码的可能性,你可以在回调中做任何事,并且在不同的地方进行调用,你也将得到不同的反馈,如果将一个函数执行过程看成一个时间轴,那回调函数就可以允许你在这个过程中的各个时刻内执行其他代码,例如你可以将Vue.js的生命周期钩子函数看成是不同时期的回调函数
回调函数的作用显而易见,但如果一层回调套一层回调的话,代码极其不方便阅读与修改,因此ES6提供了Promise构造函数,从形式上改善了回调地狱。
var a;
var p = new Promise(function (resolve,reject){
setTimeout(function (){
a = 10;
resolve();//reject也就是失败时对应的函数
},1000)
}).then(function (){
console.log(a)
})
现在我们就以一个人起床后的行为为例来深入理解异步执行机制,来看代码:
getup();
wearClothes();
brushTeeth();
washFace();
eatBreakfast();
goToCompany();
因为js是异步执行的,所以如果这么写,逻辑上等同于一个人同时开始做“起床+穿衣+刷牙+洗脸+吃早饭+出门上班”这些事,显然是不可能的事情,与我们想要的结果大相径庭,依照回调函数的写法,我们会在上述每个行为结束之后(即函数执行完毕的我们想要让他做什么,例如$.ajax()的success)传入一个回调函数,并将下一步的函数写进回调里,环环相扣,写法比较恶心,相当难以维护。
var p = new Promise(function (resolve){
getUp(function (){
resolve();
});
}).then(function (){
return new Promise(function (resolve){
wearClothes(function (){
resolve();
});
})
}).then(function (){
return new Promise(function (resolve){
brushTeeth(function (){
resolve();
});
})
}).then(function (){
return new Promise(function (resolve){
washFace(function (){
resolve();
});
})
}).then(function (){
return new Promise(function (resolve){
eatBreakfast(function (){
resolve();
});
})
}).then(function (){
goToCompany()
})
如果要链式调用then的话,必须要注意的一点是,一定要在then里的回调函数return一个新的promise对象,否则执行顺序将向上开始查找,直到找到最近的promise实例。
写到这里大家会发现promise并没有改变什么,只是把异步代码的书写形式从水平方向改为了竖直方向,让代码更符合人类的阅读习惯