阮一峰網絡日志:http://www.ruanyifeng.com/blog/2015/04/generator.html
什么是異步
不連續的執行即為異步。 簡單的說,異步就是一個任務分成兩段,先執行第一段,然后轉而執行其他任務,等做好了準備,再回過頭執行第二段。
比如,有一個任務是讀取文件進行處理,異步的執行過程就是下面這樣:
同步:連續的執行即為同步。一個任務開始執行到結束,中間不能插入其他任務。
ES6之前的異步編程方法
1. 回調函數
- 回調函數callback
把任務的第二段寫在一個函數里,等任務重新執行時就重新調用該函數。
fs.readFile('/etc/passwd', function (err, data) {
if (err) throw err;
console.log(data);
});
上面代碼中,readFile 函數的第二個參數就是回調函數,即任務的第二段。等操作系統返回文件“/etc/passwd ”后,回調函數才會執行。
Node.js 約定,回調函數的第一個參數,必須是錯誤對象err(如果沒有錯誤,該參數就是 null)。
原因是執行分成兩段,在這兩段之間拋出的錯誤,程序無法捕捉,只能當作參數,傳入第二段
- callback hell http://callbackhell.com/
callback hell: 回調函數噩夢
fs.readFile(fileA, function (err, data) {
fs.readFile(fileB, function (err, data) {
// ...
});
});
回調函數嵌套,可能導致代碼是橫向發展的,這樣子的代碼就會發生混亂很難管理。
- Promise : 回調函數的改進
1、Promise 允許將回調函數的橫向加載,改成縱向加載.
2、Promise 提供 then 方法加載回調函數,catch方法捕捉執行過程中拋出的錯誤
3、使用then方法以后,異步任務的兩段執行看得更清楚
var readFile = require('fs-readfile-promise');
readFile(fileA)
.then(function(data){
console.log(data.toString());
})
.then(function(){
return readFile(fileB);
})
.then(function(data){
console.log(data.toString());
})
.catch(function(err) {
console.log(err);
});
Promise會導致代碼冗余,過多的then會導致語義不清晰
2. 協程 https://en.wikipedia.org/wiki/Coroutine
協程,即多個線程完成協作,完成異步任務。協程有點像函數,又有點線程,其運行流程如下:
第一步,協程A開始執行。
第二步,協程A執行到一半,進入暫停,執行權轉移到協程B。
第三步,(一段時間后)協程B交還執行權。
第四步,協程A恢復執行。
上面流程的協程A,就是異步任務,因為它分成兩段(或多段)執行
function asnycJob() {
// ...其他代碼
var f = yield readFile(fileA);
// ...其他代碼
}
yield命令:表示執行暫停,執行權交給其他協程,是異步兩個階段的分界線。
上面代碼的函數 asyncJob 是一個協程,它的奧妙就在其中的 yield 命令。它表示執行到此處,執行權將交給其他協程。也就是說,yield命令是異步兩個階段的分界線。
2. Gennerator函數
Generator 函數可以暫停執行和恢復執行,這是它能封裝異步任務的根本原因
Generator函數是協程在 ES6 的實現,最大特點就是可以交出函數的執行權(即暫停執行) function* gen(x){
var y = yield x + 2;
return y;
}
上面代碼就是一個 Generator 函數。它不同于普通函數,是可以暫停執行的,所以函數名之前要加星號,以示區別 整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操作需要暫停的地方,都用 yield 語句注明
Generator 函數的執行方法如下:
var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }
上面代碼中,調用 Generator 函數,會返回一個內部指針(即遍歷器 )g 。這是 Generator 函數不同于普通函數的另一個地方,即執行它不會返回結果,返回的是指針對象。調用指針 g 的 next 方法,會移動內部指針(即執行異步任務的第一段),指向第一個遇到的 yield 語句,上例是執行到 x + 2 為止。
換言之,next 方法的作用是分階段執行 Generator 函數。每次調用 next 方法,會返回一個對象,表示當前階段的信息( value 屬性和 done 屬性)。value 屬性是 yield 語句后面表達式的值,表示當前階段的值;done 屬性是一個布爾值,表示 Generator 函數是否執行完畢,即是否還有下一個階段。
Generator 函數的數據交換和錯誤處理
Generator 函數還有兩個特性,使它可以作為異步編程的完整解決方案:函數體內外的數據交換和錯誤處理機制
next方法返回值的value 屬性,是 Generator 函數向外輸出數據;next 方法還可以接受參數,這是向 Generator 函數體內輸入數據
function* gen(x){
var y = yield x + 2;
return y;
}
var g = gen(1);
g.next() // { value: 3, done: false }
g.next(2) // { value: 2, done: true }
上面代碼中,第一個 next 方法的 value 屬性,返回表達式 x + 2 的值(3)。第二個 next 方法帶有參數2,這個參數可以傳入 Generator 函數,作為上個階段異步任務的返回結果,被函數體內的變量 y 接收。因此,這一步的 value 屬性,返回的就是2(變量 y 的值)