為了解決回調(diào)問題,es6引入了原生的迭代器,所以本文主要探討的是什么是迭代器,迭代器又怎么解決回調(diào)問題。
迭代器
迭代器是設計模式的一種,迭代器的核心方法是 hasNext
和 next
,hasNext
判斷函數(shù)內(nèi)部有沒有下一個變量,next
代表偏移到下一個變量,并且返回結果
迭代器的應用場景在于:屏蔽數(shù)據(jù)結構集合的各種差異,對于接口只要實現(xiàn)了hasNext
和next
的api,就可以成功處理各種結果。
var Iterator = function (data) {
this.curr = 0;
this.data = data;
}
Iterator.prototype.hasNext = function () {
return (this.data.length - 1) > curr;
}
Iterator.prototype.next = function () {
var ret;
if (!this.hasNext) {
return;
}
ret = this.data[this.curr];
this.curr += 1;
return ret;
}
var arrs = [1,2,3,4,5];
var iterator = new Iterator(arrs);
for (var i = 0; i < arrs.length + 1; i++) {
//1
//2
//3
//4
//5
//undefined
console.log(iterator.next());
}
對于日常應用來說,迭代起經(jīng)常被利用成為遍歷數(shù)組的工具,如jquery
$('li').each(function (index) {
console.log(index + ': ' + $(this).text());
});
回到es6標準當中,引入了Generator函數(shù),Generator是一個普通函數(shù),但是有兩個特征,1,在function命令到函數(shù)名之間有一個星號,2.函數(shù)內(nèi)可以使用yield語句,通過yield把結果拋出來。
function* Hello () {
yield 1;
yield 2;
}
var hello = Hello();
var a = hello.next();
var b = hello.next();
var c = hello.next();
//{ value: 1, done: false } { value: 2, done: false } { value: undefined, done: true }
console.log(a, b, c);
next的方法就是遍歷在yield語句產(chǎn)生的內(nèi)部狀態(tài)。每次調(diào)用便利器的next方法時,就會從函數(shù)頭部或者上一次停下來的地方繼續(xù)執(zhí)行。
第一次調(diào)用 hello.next()
next方法返回一個對象,value是當前yield語句的值1
,done屬性值false,表示遍歷沒有結束。
第二次調(diào)用,程序將會從yield 1
后,繼續(xù)執(zhí)行, value是yiled語句拋出的值2
, done屬性還是false,表示遍歷沒結束
第三次調(diào)用,函數(shù)將會從yield 2
后,一直執(zhí)行到return語句,如果沒有return語句,函數(shù)就直接結束,返回next對象 value 是 undefined
, done屬性是true,代表遍歷已經(jīng)結束。
總結一下,Generator函數(shù)使用iterator接口,每次調(diào)用next方法的返回值,就是一個標準的iterator返回值:有著value和done兩個屬性的對象。其中,value是yield語句后面那個表達式的值,done是一個布爾值,表示是否遍歷結束。
迭代器如何解決回調(diào)問題
我們先來看看一個經(jīng)典回調(diào)例子代碼
function delay(time, cb) {
setTimeout(function () {
cb && cb();
}, time);
}
console.time('1')
delay(200, function () {
delay(500, function () {
delay(100, function () {
console.timeEnd('1'); //1: 804ms
});
});
});
擁有了迭代器的我們,擁有了暫停的功能。
基本解決回調(diào)的思路關鍵在于,充分運用迭代器的next方法,當我們完成了一件任務后,我們運行一下next方法,例如上面例子當然,當我們delay了 200ms的時候,我們調(diào)用next,函數(shù)將會執(zhí)行delay 500ms。
說到這里不得不說一個很出名的小函數(shù)co
function co(GenFunc) {
return function (cb) {
var gen = GenFunc();
next();
function next(args) {
if (gen.next) {
var ret = gen.next(args);
if (ret.done) {
cb && cb(args);
} else {
ret.value(next);
}
}
}
}
}
GenFunc 是 Generator 函數(shù),通過 Generator 對象返回的next 和 value 來控制整個異步流,co 函數(shù)會 GenFunc 檢測 next的情況,如果有可以運行的next的方法,那么就會一直執(zhí)行下去,下面我們可以看看它的應用
function delay(time) {
return function (cb) {
setTimeout(function () {
cb();
}, time)
}
}
console.time('1');
co(function* () {
yield delay(200);
yield delay(1000);
yield delay(200);
})(function () {
console.timeEnd('1');
});
再co的匿名函數(shù)里面,能夠產(chǎn)生類似同步執(zhí)行的效果,完全消滅了回調(diào),當函數(shù)執(zhí)行完成后,調(diào)用 console.timeEnd。
全部示例代碼: https://github.com/youyudehexie/nodejs-cookbook/tree/master/yield
后記
- 為了配合co模塊的使用,需要將迭代器執(zhí)行的函數(shù),改造成如下格式
function delay(time) {
return function (cb) {
setTimeout(function () {
cb();
}, time)
}
}
有一段時間里面,曾經(jīng)誤認為Generator 是node.js的 新版本特性,而實際上僅僅是v8引擎對于協(xié)議的實現(xiàn)而已。
Generator 引入了協(xié)程的概念,當在Generator 完成了任務后,將會將函數(shù)結果放到內(nèi)存,能夠擁有多個調(diào)用棧,當我們執(zhí)行next的時候,會把結果取出,相對于es5以前的實現(xiàn),es5只有一個調(diào)用棧,es5每次回調(diào)出錯的時候,調(diào)用棧有時候會被沖走。
關于異步的篇章到目前為止,該算是結束,毫無疑問,es6的解決方案是最優(yōu)秀的。希望能快點把es6普及起來把