javascript异步编程第一步:iterator和generator
zzzmj opened this issue · 0 comments
前言
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
那么我们一步步的从迭代器到生成器,来看看generator的优点和缺点
迭代器
首先简单了解一下迭代器的概念
迭代器(iterator)
迭代器,下面是一个简单的迭代器,迭代一个数组
var iterator = function(arr) {
var index = 1
return {
next: function() {
if (index < arr.length) {
return {
value: arr[index++],
done: false,
}
} else {
return {
done: true,
}
}
}
}
}
var it = iterator([1, 2, 3, 4])
it.next() // 1
it.next() // 2
可以看到这个迭代器的作用,我们给数组[1,2, 3, 4]
创建了一个迭代器,然后每次调用迭代器的next
方法,就可以指向数组的下一个元素。
但是这种方法显然比较麻烦
- 我们需要自己显式去定义迭代器的方法,去维护数组的状态
- 而且对于其他类型的对象,我们这个玩具迭代器也很难复用
那我们就可以通过了解更高级的做法,生成器generator
生成器(generator)
先简单了解一下generator
的语法,通过阮老师的文章Generator 函数的语法
我们需要知道几个关键的地方是:
generator
函数调用后,函数不会执行,而是返回一个迭代器对象,替我们维护函数内部状态- 调用迭代器对象的
next
方法后,函数开始执行,直到遇到yield
表达式(或者return语句)的时候停止 yield
后面的表达式值,作为next
方法返回值中的value属性值
于是我们可以知道generator
,可以很好的解决迭代器的两个问题,它会自动的维护创建的状态,请看下面的简单例子
var generator = function* (arr) {
for(let i = 0; i < arr.length; i++) {
yield arr[i]
}
}
var it = generator([1, 2, 3, 4, 5])
it.next() // {value: 1, done: false}
it.next() // {value: 2, done: false}
it.next() // {value: 3, done: false}
// ...
显而易见,generator做到了与迭代器一样的事情,神奇的是
generator配合yield做到了'暂停代码'的功能,让代码像进度条样,一点一点执行
(对运行机制感兴趣可以使用Babel将generator代码转换为ES5形式,它其实是模拟了一个状态机)
利用这个特性,我们可以以同步的方式来执行异步代码
我们造一个需求,使用node中的readFile方法依次读取三份文件1.txt, 2.txt, 3.txt的内容(要求使用generator)
这个地方还有一个小知识点,
fs.readFile
函数没有返回值,文件的内容在回调函数中传出,那我们怎么将值传递给yield呢?
答案是写在next参数里,当yield表达式本身没有返回值。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
所以我们可以这样实现
var fs = require('fs')
var dealCallback = (err, data) => {
console.log('data', data.toString())
gen.next(data)
}
var read = function* (arr) {
yield fs.readFile('1.txt', dealCallback)
yield fs.readFile('2.txt', dealCallback)
yield fs.readFile('3.txt', dealCallback)
}
var gen = read()
gen.next()
// 依次打印
// 1.txt
// 2.txt
// 3.txt
但是这样做的缺点很明显
- 需要显式的以gen.next()来启动
- 转交下一步流程控制需要写到回调中,而且遇到不同的异步代码时,难以通用
很麻烦,其实我们想要的就是运行read函数,然后按照顺序执行我们的代码而言
有什么方法可以让它变的更简单些呢?
请看下一篇: javascript异步编程第二步:co库