zzzmj/duola-blog

javascript异步编程第一步:iterator和generator

zzzmj opened this issue · 0 comments

zzzmj commented

前言

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方法,就可以指向数组的下一个元素。

但是这种方法显然比较麻烦

  1. 我们需要自己显式去定义迭代器的方法,去维护数组的状态
  2. 而且对于其他类型的对象,我们这个玩具迭代器也很难复用

那我们就可以通过了解更高级的做法,生成器generator

生成器(generator)

先简单了解一下generator的语法,通过阮老师的文章Generator 函数的语法

我们需要知道几个关键的地方是:

  1. generator函数调用后,函数不会执行,而是返回一个迭代器对象,替我们维护函数内部状态
  2. 调用迭代器对象的next方法后,函数开始执行,直到遇到yield表达式(或者return语句)的时候停止
  3. 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

但是这样做的缺点很明显

  1. 需要显式的以gen.next()来启动
  2. 转交下一步流程控制需要写到回调中,而且遇到不同的异步代码时,难以通用

很麻烦,其实我们想要的就是运行read函数,然后按照顺序执行我们的代码而言
有什么方法可以让它变的更简单些呢?

请看下一篇: javascript异步编程第二步:co库