ES6迭代器与生成器入门

117 阅读5分钟

迭代器

迭代器的定义:迭代器是一种有序的、连续的、基于拉取的用于消耗数据的组织方式。

迭代协议

迭代协议具体分为两个协议:可迭代协议 和 迭代器协议。

可迭代协议

可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of结构中,哪些值可以被遍历到。一些内置类型同时是内置可迭代对象,并且有默认的迭代行为,比如 Array 、StringMap,而其他内置类型则不是(比如 Object))。

要成为可迭代对象, 一个对象必须实现 @@iterator 方法。这意味着对象必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性:

迭代器协议

迭代器协议定义了产生一系列值的标准方式。
只有实现了一个拥有以下语义的 **next()** 方法,一个对象才能成为迭代器:
属性为next,值为一个无参数函数,返回一个应当拥有以下两个属性的对象:

{
	done // 如果迭代器可以产生下一个值,则为 false,如果迭代器已迭代完毕,则为 true
  value // 迭代器返回的值
}

迭代数组

var arr = [1,2,3];
var it = arr[Symbol.iterator]();
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: false }
it.next(); // { value: undefined, done: true }

迭代字符串

var greeting = "hello world";
var it = greeting[Symbol.iterator]();
it.next(); // { value: "h", done: false }
it.next(); // { value: "e", done: false }

迭代集合

var m = new Map();
m.set( "foo", 42 );
m.set( { cool: true }, "hello world" );
var it1 = m[Symbol.iterator]();
var it2 = m.entries();
it1.next(); // { value: [ "foo", 42 ], done: false }
it2.next(); // { value: [ "foo", 42 ], done: false }

可选的 return(..) 和 throw(..)

多数内置迭代器都没有实现可选的迭代器接口—— return(..) 和 throw(..) 。
return(..) 被定义为向迭代器发送一个信号,表明消费者代码已经完毕,不会再从其中提取任何值。如果迭代器存在 return(..) ,并且出现了任何可以自动被解释为异常或者对迭代器消耗的提前终止的条件,就会自动调用 return(..) 

throw(..) 用于向迭代器报告一个异常 / 错误。

自定义迭代器

除了标准的内置迭代器,也可以构造自己的迭代器,比如构造一个迭代器来产生一个无限斐波纳契序列:

var Fib = {
  [Symbol.iterator]() {
    var n1 = 1, n2 = 1;
      return {
        // 使迭代器成为iterable
        [Symbol.iterator]() { return this; },
        next() {
          var current = n2;
          n2 = n1;
          n1 = n1 + current;
          return { value: current, done: false };
        },
        return(v) {
          console.log("Fibonacci sequence abandoned.");
        	return { value: v, done: true };
      	}
    };
  }
};

手动消耗迭代器:

let it = Fib[Symbol.iterator]()
it.next() //{value: 1, done: false}
it.next() //{value: 1, done: false}
it.next() // {value: 2, done: false}
it.next() //{value: 3, done: false}
it.next() //{value: 5, done: false}
...

使用 for of 消耗迭代器:

for (var v of Fib) {
  console.log( v );
  if (v > 50) break;
}
// 1 1 2 3 5 8 13 21 34 55
// Fibonacci sequence abandoned.

生成器

 ES6 引入了一个全新的函数形式,称为生成器。生成器可以在执行当中暂停自身,可以立即恢复执行也可以过一段时间之后恢复执行。

语法

声明一个生成器函数:

function *foo() { .. }
*的位置无所谓。同样的声明可以写作:
function* foo() { .. }
function * foo() { .. }
function*foo() { .. }

yield关键词
生成器还有一个可以在其中使用的新关键字,用来标示暂停点: yield 。

function *foo() {
  var x = 10;
  var y = 20;
  yield;
  var z = x + y;
}

在这个生成器中,首先执行前两行操作,然后 yield 会暂停这个生成器。如果恢复
的话,会运行 *foo() 的最后一行

也可以yield一个表达式:

function *foo() {
  var x = yield 10;
}

运行生成器

尽管生成器用 * 声明,但执行起来还和普通函数一样:foo()
主要的区别是,执行生成器并不实际在生成器中运行代码。相反,它会产生一个迭代器控制这个生成器执行其代码。
调用 next()方法时,如果传入了参数,那么这个参数会传给上一条执行的 yield语句左边的变量

function *foo() {
  var x = yield 1;
  var y = yield 2;
  var z = yield 3;
  console.log( x, y, z );
}
var it = foo();
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: false }
it.next(); // { value: undefined, done: true }

运行过程分析
var it = foo();
it.next(); // { value: 1, done: false }


1,第一个 next() 调用初始的暂停状态启动生成器,运行直到第一个 yield 。在调用第一个next() 的时候,并没有 yield.. 表达式等待完成。如果向第一个 next() 调用传入一个值,这个值会马上被丢弃,因为并没有 yield 等待接收这个值。

2,这里有一个问题,x 的值应该是什么?我们通过发送一个值给下一个 next(..) 调用来回答这个问题:

it.next( "foo" ); // { value: 2, done: false }

现在, x 的值就是 "foo"
3,y的值是什么?

it.next( "bar" ); // { value: 3, done: false }

y的值为"bar"
4,接着往下运行:

it.next( "baz" )  // { value: undefined, done: true }

z的值为"baz"

完整的过程:

var it = foo();
// 启动生成器
it.next(); // { value: 1, done: false }
// 回答第一个问题
it.next( "foo" ); // { value: 2, done: false }
// 回答第二个问题
it.next( "bar" ); // { value: 3, done: false }
// 回答第三个问题
it.next( "baz" ); // "foo" "bar" "baz"
// { value: undefined, done: true }

使用生成器控制异步流程

step1()
  .then(
    step2,
    step2Failed
  )
  .then(
    function (msg) {
      return Promise.all([
        step3a(msg),
        step3b(msg),
        step3c(msg)
      ])
    }
  )
  .then(step4);

虽然使用promise可以处理异步流程,但还有一种更好的方案可以用来表达异步流控制,用生成器来表达前面代码片段的异步流控制:

function* main() {
  var ret = yield step1();
  try {
    ret = yield step2(ret);
  }
  catch (err) {
    ret = yield step2Failed(err);
  }
  ret = yield Promise.all([
    step3a(ret),
    step3b(ret),
    step3c(ret)
  ]);
  yield step4(ret);
}

除此之外还需要一个可以运行生成器的运行器:
大名鼎鼎的co库就是一个运行器,这里介绍另外一个:

function run(gen) {
  var args = [].slice.call(arguments, 1), it;
  it = gen.apply(this, args);
  return Promise.resolve()
    .then(function handleNext(value) {
      var next = it.next(value);
      return (function handleResult(next) {
        if (next.done) {
          return next.value;
        }
        else {
          return Promise.resolve(next.value)
            .then(
              handleNext,
              function handleErr(err) {
                return Promise.resolve(
                  it.throw(err)
                )
                  .then(handleResult);
              }
            );
        }
      })(next);
    });
}

运行生成器:

run( main )
.then(
  function fulfilled(){
  },
  function rejected(reason){
  }
);

最后

由于生成器语法比较复杂,并且需要一个运行器(run)来运行,因此JS引入了一个新的函数类型来自动执行这种模式而无需 run 工具。 也叫作 async function

参考资料:
1,developer.mozilla.org/zh-CN/docs/…
2,developer.mozilla.org/zh-CN/docs/…
3,你不知道的js(下)