《深入理解ES6》阅读笔记 --- 迭代器和生成器

《深入理解ES6》阅读笔记 --- 迭代器和生成器

这一小节的内容,比较鼓舞的是终于可以在JS语言层面,能看见Iterator和Generator了。说到迭代器,也许你会有疑问,可以预期的,你能看到Generator的实现也是依赖迭代器。我所接触到的编程语言中,最早让我理解这个特性的是Python。迭代器是一种特殊的对象,它的设计有专门的接口(描述)来完成我们常说的迭代(循环)过程。

每一个迭代器对象,都具备next方法(当然它也具备一些比如throw方法),当你执行next方法时,会返回一个(描述)对象,这个对象中,存在value,done属性,你想要的值就是value,而done则是用来描述整个迭代过程是否结束,可以想象,当迭代过程结束之后,value的值必然会是一个“空”(不同语言的描述不同),JS会给你一个undefined。

目前我们所知道的数组,Set,Map这三个集合是会存在迭代器,而且它们都有内建的迭代器:entries,values,keys。不知道,你在前几个小节中是否有印象Symbol中存在一个iterator属性,如果你想知道,不妨翻一翻之前分享的Symbol。在这里,我们可以通过Symbol.iterator来获取默认的迭代器(如果具备Symbol.iterator属性,那么意味着它必然具备默认的迭代器)。

let sets = [1,2,3]
let iterator = sets[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

如果你不想调用三下迭代器,你可以自己写一个小的循环,比如:

let res = iterator.next();
while(!res.done){
  res = iterator.next()
  console.log(res)
}

所谓的Generator也就是一种可以返回迭代器的函数,只不过它用*和yield来表示(描述过程)。

function * createIterator(){
   yield 1;
   yield 2;
}

let iterator = createIterator()

console.log(iterator.next())
console.log(iterator.next())

其实这很好理解,(语言)在背后帮我们对这个函数进行了包装,每一个yield都会返回一个迭代器对象,你想执行真正的逻辑,或者你想获取值,都需要通过这个迭代器对象来获取。

Generator可以辅助我们完成很多复杂的任务,而这些基础知识,又与iterator息息相关,举一个很简单的例子,相信有很多朋友,应该使用过co这个异步编程的库,它就是用Generator来实现,当然它的设计会比例子要复杂的多,我们先来看一个co简单的用法:

import co from 'co'
co(function* () {
  var result = yield Promise.resolve(true);
  return result;
}).then(function (value) {
  console.log(value);
}, function (err) {
  console.error(err.stack);
});

相应的,我们来实现一个简化的版本:

function co(task){
  let _task = task()
  let resl = _task.next();
  while(!resl.done){
    console.log(resl);
    resl = _task.next(resl.value);
  }
}

function sayName(){
  return {
    name: 'icepy'
  }
}

function assign *(f){
  console.log(f)
  let g = yield sayName()
  return Object.assign(g,{age:f});
}

co(function *(){
  let info = yield *assign(18)
  console.log(info)
})

虽然,这个例子中,还不能很好的看出来“异步”的场景,但是它很好的描述了Generator的使用方式。

从最开始的定义中,已经和大家说明了,Generator最终返回的依然是一个迭代器对象,有了这个迭代器对象,当你在处理某些场景时,你可以通过yield来控制,流程的走向。通过co函数,我们可以看出,先来执行next方法,然后通过一个while循环,来判断done是否为true,如果为true则代表整个迭代过程的结束,于是,这里就可以退出循环了。在Generator中的返回值,可以通过给next方法传递参数的方式来实现,也就是遇上第一个yield的返回值。

有逻辑,自然会存在错误,在Generator捕获错误的时机与执行throw方法的顺序有关系,一个小例子:

let hu = function *(){
  let g = yield 1;
  try {
    let j = yield 2;
  } catch(e){
    console.log(e)
  }
  return 34
}

let _it = hu();

console.log(_it.next())
console.log(_it.next())
console.log(_it.throw(new Error('hu error')))

当我能捕获到错误的时机是允许完第二次的yield,这个时候就可以try了。

Iterator和generator给了我们很多启发,在编程的维度上,我能想到的就是去处理异步代码时为我们提供便捷的方式。当然迭代器,这个方向上,可以做的事情有很多,如果你悉心去寻找,相信,很快能找到答案。

编辑于 2018-08-12

文章被以下专栏收录