为了解决回调问题,es6引入了原生的迭代器,所以本文主要探讨的是什么是迭代器,迭代器又怎么解决回调问题。
迭代器
迭代器是设计模式的一种,迭代器的核心方法是 hasNext
和 next
,hasNext
判断函数内部有没有下一个变量,next
代表偏移到下一个变量,并且返回结果
迭代器的应用场景在于:屏蔽数据结构集合的各种差异,对于接口只要实现了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());
}
对于日常应用来说,迭代起经常被利用成为遍历数组的工具,如jquery
$('li').each(function (index) {
console.log(index + ': ' + $(this).text());
});
回到es6标准当中,引入了Generator函数,Generator是一个普通函数,但是有两个特征,1,在function命令到函数名之间有一个星号,2.函数内可以使用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语句产生的内部状态。每次调用便利器的next方法时,就会从函数头部或者上一次停下来的地方继续执行。
第一次调用 hello.next()
next方法返回一个对象,value是当前yield语句的值1
,done属性值false,表示遍历没有结束。
第二次调用,程序将会从yield 1
后,继续执行, value是yiled语句抛出的值2
, done属性还是false,表示遍历没结束
第三次调用,函数将会从yield 2
后,一直执行到return语句,如果没有return语句,函数就直接结束,返回next对象 value 是 undefined
, done属性是true,代表遍历已经结束。
总结一下,Generator函数使用iterator接口,每次调用next方法的返回值,就是一个标准的iterator返回值:有着value和done两个属性的对象。其中,value是yield语句后面那个表达式的值,done是一个布尔值,表示是否遍历结束。
迭代器如何解决回调问题
我们先来看看一个经典回调例子代码
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
});
});
});
拥有了迭代器的我们,拥有了暂停的功能。
基本解决回调的思路关键在于,充分运用迭代器的next方法,当我们完成了一件任务后,我们运行一下next方法,例如上面例子当然,当我们delay了 200ms的时候,我们调用next,函数将会执行delay 500ms。
说到这里不得不说一个很出名的小函数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 函数,通过 Generator 对象返回的next 和 value 来控制整个异步流,co 函数会 GenFunc 检测 next的情况,如果有可以运行的next的方法,那么就会一直执行下去,下面我们可以看看它的应用
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的匿名函数里面,能够产生类似同步执行的效果,完全消灭了回调,当函数执行完成后,调用 console.timeEnd。
全部示例代码: https://github.com/youyudehexie/nodejs-cookbook/tree/master/yield
后记
- 为了配合co模块的使用,需要将迭代器执行的函数,改造成如下格式
function delay(time) {
return function (cb) {
setTimeout(function () {
cb();
}, time)
}
}
有一段时间里面,曾经误认为Generator 是node.js的 新版本特性,而实际上仅仅是v8引擎对于协议的实现而已。
Generator 引入了协程的概念,当在Generator 完成了任务后,将会将函数结果放到内存,能够拥有多个调用栈,当我们执行next的时候,会把结果取出,相对于es5以前的实现,es5只有一个调用栈,es5每次回调出错的时候,调用栈有时候会被冲走。
关于异步的篇章到目前为止,该算是结束,毫无疑问,es6的解决方案是最优秀的。希望能快点把es6普及起来把