异步编程对于开发新手来讲,简直是噩梦般存在,可是又很是重要,非学不可。其实啊,异步调用,并无那么可怕。javascript
所谓的异步,简单来讲,就是任务不是一次连续完成的,中间加入了其余的程序运算,等第一阶段准备好了数据,再返回来进行计算。前端
回调函数的名字,callback,这个相信作过前端开发的小伙伴都见到过。javaScript语言对异步编程的实现就是回调函数。
举个例子:java
fs.readFile('etc/passwd','utf-8',function(err,data){
if(err) throw err;
console.log(data);
});
复制代码
回调函数自己没有问题,问题在于,出现多个回调函数嵌套上,也就是咱们俗称的回调地狱。node
let url1 = 'http://xxx.xxx.1';
let url2 = 'http://xxx.xxx.2';
let url3 = 'http://xxx.xxx.3';
$.ajax({
url:url1,
error:function (error) {},
success:function (data1) {
console.log(data1);
$.ajax({
url:url2,
data:data1,
error:function (error) {},
success:function (data2) {
console.log(data2);
$.ajax({
url:url3,
data,
error:function (error) {},
success:function (data3) {
console.log(data3);
}
});
}
});
}
});
复制代码
这种代码自己逻辑能够实现,问题处在下面几个方面:git
在之前的章节中,咱们讲到过Promis函数,他就是典型的解决函数异步问题而产生的。 他是回调函数的新写法。程序员
举个例子:github
function request(url,data = {}){
return new Promise((resolve,reject)=>{
$.ajax({
url,
data,
success:function (data) {
resolve(data);
},
error:function (error) {
reject(error);
}
})
});
}
let url1 = 'http://xxx.xxx.1';
let url2 = 'http://xxx.xxx.2';
let url3 = 'http://xxx.xxx.3';
request(url1)
.then((data)=>{
console.log(data);
return request(url2,data)
})
.then((data)=>{
console.log(data);
return request(url3,data)
})
.then((data)=>{
console.log(data)
})
.catch((error)=>{
console.log(error);
});
复制代码
相对上面来讲,使用Promis对象后,代码结构变得清晰的许多。但,问题是,一眼看上去,多了好多个then,并且,不写注释,很难明白究竟是什么意思。ajax
generator函数是ES6提供的一种异步编程解决方案,语法与传统函数彻底不一样。 执行Generator函数会返回一个遍历器对象。返回的遍历器对象能够依次遍历Generator函数内部的每个状态shell
上面的章节中,咱们提到过,Genertor函数,最大的特色,就是能够交出函数的执行权。也就是说,能够暂停执行函数。编程
function * gen(x) {
var y = yield x + 1;
return y;
}
var g = gen(1);
g.next(); // {value:2,done:false}
g.next(); // {value:undefined.done:true}
复制代码
执行gen函数,返回的是函数指针,调用指针函数的next方法能够移动内部函数的指针,指向第一个遇到的yield语句。
下面看一个使用Generator函数执行一个真实的异步任务。
var fetch = require('node-fetch);
function * gen() {
var url = 'http://api.github.com';
var result = yield fetch(url);
console.log(result.bio);
}
var g = gen();
var result = g.next();
result.value.then(function(data) {
return data.JSON();
}).then(function(data) {
g.next(data);
})
复制代码
上面的代码将异步表达的很清晰,可是流程管理并不方便,还的本身调用next方法。
例子:
function * gen() {
//...
}
var g = gen();
var res = g.next();
while(!res.done){
console.log(res.value);
g.next()
}
复制代码
上面代码能自动完成,前提是不适合异步,若是必须保证前一步执行完才能执行后一步,上面的自动执行就不行了。
如何自动完成异步函数呢?引出咱们的主角,thunk函数。
var fs = requier('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);
var gen = function *() {
var r1 = yield readFileThunk('/etc/fstab');
console.log(r1.toString());
var r2 = yield readFileThunk('/etc/shells');
console.log(r2.toString());
}
复制代码
上面代码,执行异步操做后(yield 后面的表达式),须要将执行权再返回给Generator函数,咱们先手动执行一下。
var g = gen();
var r1 = g.next();
r1.value(function(err,data) {
if(err) throw err;
var r2 = g.next(data);
r2.value(function(err,data) {
if(err) throw err;
g.next(data);
})
})
复制代码
仔细看一下,上面的代码,generator函数执行过程其实就是将同一个回调函数反复传入next方法的value属性。
如何自动完成这个操做呢?
function run (fn) {
var gen = fn();
function next(err,data) {
var result = gen.next(data);
if(result.done) return ;
result.value(next)
}
next();
}
function* g() {
//...
}
run(g);
复制代码
上面代码中的run函数就是一个Generator函数的自动执行器。内部的next函数就是Thunk的回调函数,next先将指针移动到Generator函数的下一步,而后判断Generator函数是否结束, 若是没结束,就将next函数再传入thunk函数,不然直接退出。
这就方便多了。只需将要执行的函数,放入run()函数就能够了。妈妈不再担忧个人异步调用了。
每次都写run函数么?很烦馁~
那就尝试一下CO。
co模块是著名的程序员TJ Holowaychuk于2013年6月发布的一个小工具,用于Generator函数自动执行。
怎么用呢?至关简单了。
var co = require('co');
co(gen);
复制代码
至关简便。并且,co函数返回的是一个Promise对象,能够调用then方法添加回调函数。
co(gen).then(function() {
console.log('到这函数就都执行完了。')
});
复制代码
generator函数是一个异步执行容器。它的自动执行须要一中机制,当异步操做有告终果,须要自动交回执行权。
解释一下:
function * gen(x) {
var y = yield x = 1;
var z = yield 1+1;
return y, z;
}
复制代码
当执行到第一个yield的时候,调用next方法,获取到值。再次调用next,继续执行,再次调用这步骤,不用人为调用了。
co模块其实就是将thunk函数和promise对象包装成一个模块。使用的前提是,yield后面的表达式必须是thunk函数或者promise对象。4.0以后的co版本只支持promise对象了。
有时间的同窗能够看看co的源码,也很简单的,这里就不作过多的介绍了。