随着js的发展,在解决回调地狱问题的方案上,解决方案逐渐更新,有promise、generator和现在的async都是比较常见的;
1.Promise
这个解决方案就是把异步用同步的方式写出来,一步一步的.then()
方法,前一个.then()执行完之后,继续执行下一个.then()
;
function timeout (time) {
return new Promise((res, rej)=>{
setTimeout(()=>{
res('this is a returnDate');
},time)
})
};
time(5000)
.then((data)=>{console.log(data)})
.then(()=>{console.log('end')})
...
这就是promise的异步解决方案看起来还是很简单的,其实只是以你为逻辑简单;
2.Generator
Generator函数是个异步管理器,这里要提到一个协程的概念,先不用管协程是什么鬼,先来介绍一下yield
命令,它是Generator函数中的分水岭,在函数内部起执行权转义的功能,什么是执行权?好吧,这就是在Generator函数内部遇到yield命令,那么就不往下执行了,就把执行权交出来给别的函数,等别的函数执行之后返回结果了,在返还执行权,继续向下执行Generator函数的语句;
function generator () {
// 代码A;
var f = yield readFile(fileA);
// 代码A;
}
以上代码中,当函数执行的时候分一下步骤:
a.开始执行代码A;
b.代码A执行到一半,遇到yield命令,暂停执行,开始执行,执行权交给yield
命令后面的函数;
c.当yield函数执行完之后,交还执行权;
d.继续执行yield
命令下面的代码A;
在这个过程中,代码段A被分成两段执行,代码段A叫做协程A,readFile()
函数叫做协程B,协程A是异步的;
再来举个例子:
function* gen(x){
var y = yield x+2;
return y
}
var g = gen(1);
g.next() // { value:3, done:false }
g.next() // { value:undefined, done:ture }
a.调用Generator函数并不会像其他普通函数一样返回函数值,它会返回一个内部指针——g;
b.调用内部指针中的next
方法,会移动指针分配执行权去手动执行函数;返回结果会是一个对象,value
字段对应运算值,done
字段是布尔类型,说明函数是否执行完;
c.上面代码中,第一次调用next
方法,函数执行到x+2
,值返回给value属性;第二次调用next
方法,执行代码是return y
,这时y并没有值,只是定义了,所以第二次vlaue的值是undefined
;
d.当然next方法也是可以传值的,如下:
var g = gen(1);
g.next() // { value:3, done:false }
g.next(2) // { value:2, done:ture }
这时第二个next
方法带有参数2,这个参数会传到Generator函数内部,作为上个协程任务的返回结果被函数体内的变量y接收,因此value的值是2;
对了这里在补一句,从上可以看出Generator函数是需要手动执行的,那么有没有可以让其自执行的办法呢,有~
co模块小工具可以让Generator函数自执行,只要把其当参数传进co中;
var co = require('co');
co(gen);
3.async函数
这个es7草案,说实话也是最简单的异步解决方案,问其原理,其实是Generator函数的语法糖;
a:
function timeout (time) {
return new Promise((res, rej)=>{
setTimeout(()=>{
res();
},time)
})
}
let start = async function () {
console.log('start');
let data = await timeout(5000);
console.log('end');
}
执行结果是:
先打印出start
,然后转为timeout
函数执行,执行时间为5s,之后返回打印出end
;
是不是跟Generator函数很相似,await
跟yield
命令作用一样~
b.当然了,也是可以获取到返回值的:
function timeout (time) {
return new Promise((res, rej)=>{
setTimeout(()=>{
res('this is a returnDate');
},time)
})
}
let start = async function () {
console.log('start');
let data = await timeout(5000);
console.log(data);
}
这样,5s后打印出来的就是this is a returnDate
;
c.当然,也可以捕获错误:
function timeout (time) {
return new Promise((res, rej)=>{
setTimeout(()=>{
rej('this is an err');
},time)
})
}
let start = async function () {
console.log('start');
try {
let data = await timeout(5000);
console.log(data);
} catch (err) {
console.log(err);
}
}
这里timeout
函数返回一个错误,那么被try...catch...
语句中的catch
捕获,则不执行try
中的代码,直接执行catch
中的代码,打印出错误;
d.当然了,这里也是可以循环多执行await的
function timeout (time) {
return new Promise((res, rej)=>{
setTimeout(()=>{
rej('this is an err');
},time)
})
}
let start = async function () {
for(let i=0; i<=10; i++) {
console.log(`这是第${i}次等待`);
await timeout(5000);
}
}
这里指出非常重要的一点,await命令必须要在async函数的上下文中,也就是说当await命令所在的环境中this的指向不是async的时候,会报错!
所以这里要用for循环,for…of循环,不能用map,forEach方法等;