[转]JavaScript基础:生成器

ES6 的生成器特性,其他语言里边大多都有这个特性,可用于实现协程,但个人感觉大多数写异步逻辑的场景还是用 Promise 方便,原文链接:juejin.cn/post/6928660813683097613 ,作者:菩提谛听

概述

JavaScript中函数一旦启动运行,在它结束之前是不会被中断的。但在ES6中引入了一个全新的函数形式生成器Generator。这种函数不像普通函数一样保证执行运行到结束,而是具有函数块内暂停和恢复代码执行的能力。生成器Generator的形式只是在普通函数名称之前加一个*表示这是一个生成器。其特征主要有两个:1、function关键字与函数名之间有一个星号;2、函数体内部使用关键字yield表达式,定义不同的内部状态。生成器Generator的使用与普通函数一样,只是调用生成器函数会产生一个迭代器,因此可以调用迭代器的next函数让函数开始或恢复执行。

// 这是一个 Generator

function *genFunc () {
  yield 'generator';
  yield 'hello';
  yield 'word';
  return '!';
}

const it = genFunc();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

如下其执行结果就是迭代器产生的内容:

gen的log结果

yield关键字

生成器通过使用yield关键字便是暂停点。也就是说,生成器函数遇到yield关键字之前会正常执行,遇到该关键字时,执行会暂停函数的作用域状态会被保留下来,直到调用next()函数恢复执行。此时的yield关键字有点像是函数的中间返回语句,它生成的值会出现在next()返回的对象里。yield关键字只能用在生成器函数内部,其它地方会抛出错误。

// 普通函数使用 yield

function func () {
  yield '测试 yield关键字的使用';
}

报错

yield* 表达式

如何在生成器函数中调用另一个生成器函数,理论上需要在函数体内手动遍历执行另一个生成器函数。

function *gen1 () {
  yield 'gen1: hellow';
  yield 'gen1: word';
  return 'gen1';
}

function *gen2 () {
  for (const genValue of gen1()) {
    console.log(genValue);
  }
  yield 'gen2: hellow';
  yield 'gen2: word';
  return 'gen2';
}

const it = gen2();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

手动迭代的结果

ES6提供了yield*表达式即是yield委托(yield delegation)解决此类问题。yield* ...需要一个iterable对象,然后它会调用iterable的迭代器,把自己的生成器控制委托到这个迭代器,直到其耗尽为止。

function *gen3 () {
  const genValue = yield* gen1();
  console.log('genValue:', genValue);
  yield 'gen3: hellow';
  yield 'gen3: word';
  return 'gen3';
}

const it = gen3();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

yield*的结果 因此可以使用yield*表达式实现递归操作。

function *genMult (x) {
  if (x < 3) {
      x = yield* genMult(x + 1);
  }
  return x * 2;
}

const it = genMult(1);
it.next(); // {value: 24, done: true}

yield输入和输出

yield既可以作为函数的中间返回语句使用,也可以作为函数的中间参数使用。yield表达式本身没有返回值,或者说总是返回undefinednext方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

function *sum (x) {
  const y = yield;
  const z = yield;
  return x + y + z;
}

const it = sum(1);
console.log(it.next());
console.log(it.next(2));
console.log(it.next(3));

yield输入 其中第一次调用next出入的值不会被使用,因为第一次调用是为了开始执行生成器函数。

提前终止生成器

与迭代器类似可以使用returnthrow提前结束跌倒器的状态,生成器也是可以使用returnthrow方法提前终止生成器。return方法会强制生成器进入关闭状态,return方法传入的值,就是终止生成器的值。切后续关闭状态无法恢复,后续再次调用next方法done: true会一直保持。

function *returnFunc () {
  yield 1;
  yield 2;
  yield 3;
}

const it = returnFunc();
console.log(it.next());
console.log(it.return(4));
console.log(it.next());

return的log return方法除了关闭生成器之外,如何生成器函数中包含finally子句,还可以执行情路任务如何资源释放、状态重置等。也就是说如果生成器函数内部有try...finally代码块,且正在执行try代码块,那么return()方法会导致立刻进入finally代码块,执行完以后,整个函数才会结束。

function *returnFunc () {
  try {
    yield 1;
    yield 2;
  } finally {
    yield 3;
    yield 5;
  }
}

const it = returnFunc();
console.log(it.next());
console.log(it.return(4));
console.log(it.next());
console.log(it.next());

finally的值 注意调用return()方法后,就立即执行finally代码块,不执行try里余下的代码了,然后等到finally代码块执行完,再返回return()方法指定的值。

throw方法会在暂停的时候将一个提供的错误注入到生成器对象中,相当于在暂停点出入一个throw ...。如果生成器内部有catch模块对错误进行处理,那么生成器就不会关闭,而且还可以恢复执行。

function *throwFunc () {
  try {
    yield 1;
    yield 2;
  } catch (e) {
      console.error(e);
    yield 3;
    yield 5;
  }
}

const it = throwFunc();
console.log(it.next());
console.log(it.throw(new Error('throw error')));
console.log(it.next());

throw的log结果

讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!