異步編程對於開發新手來講,簡直是噩夢般存在,可是又很是重要,非學不可。其實啊,異步調用,並無那麼可怕。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的源碼,也很簡單的,這裏就不作過多的介紹了。