JavaScript 中的生成器函数

简介: 调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器对象。在生成器函数内部可以通过yield关键字来中断代码的执行,迭代器每次执行都会执行到下一次 yield 或者 return。

语法


function* name([param[, param[, ... param]]]) { statements }

  • name:函数名
  • param:要传递给函数的一个参数的名称,一个函数最多可以有 255 个参数。
  • statements:普通 JS 语句。

调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器对象。在生成器函数内部可以通过yield关键字来中断代码的执行,迭代器每次执行都会执行到下一次 yield 或者 return。

生成器函数不能作为构造器使用。

每次迭代的返回值是一个对象,结构为{value: string; done: boolean}value 是执行到的 yield 表达式的结果,done 表示迭代器是否迭代结束,false 表示迭代还在继续,true 表示迭代完成。

通过调用迭代器对象的 next 方法可以控制生成器函数的执行,next 方法可以接受参数作为当次 yield 表达式的计算结果。

function* gen(x: number): Generator<
  number,
  number,
  number
> {
  const y = yield x + 10;
  const z = yield x * y;
  return z;
}
const g = gen(2);
console.log(g.next(1000)); // { value: 12, done: false }
console.log(g.next(5)); // { value: 10, done: false }
console.log(g.next(22)); // { value: 22, done: true }
复制代码

注意,如果 yield 的结果有赋值操作需要在 TS 中生成器函数需要标注生成器函数的类型。

interface Generator<
  T = unknown,
  TReturn = any,
  TNext = unknown
> extends Iterator<T, TReturn, TNext> {
    // NOTE: 'next' is defined using a tuple to ensure 
    // we report the correct assignability errors in all places.
    next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
    return(value: TReturn): IteratorResult<T, TReturn>;
    throw(e: any): IteratorResult<T, TReturn>;
    [Symbol.iterator](): Generator<T, TReturn, TNext>;
}
复制代码

1682519182(1).png

由于每一步 yield 的结果并不会直接用于下一步计算,而是返回值的 value, 真正用于下一步计算的结果是 next 填写的参数。

第一次next 传的参数总是无效的,因为第一次中断依赖的变量只能通过形参级全局变量,没有更早的yield 中断。

如果用的是 yield*,则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。

function* anotherGenerator(i: number) {
  yield i + 1;
  yield i + 2;
  yield i + 3;
}
function* generator(i: number){
  yield i;
  yield* anotherGenerator(i);// 移交执行权
  yield i + 10;
}
var gen = generator(10);
console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20
复制代码


练习


分析下面的代码的输出结果

function* iterArr(arr) {
  if (Array.isArray(arr)) {
    for (let i = 0; i < arr.length; i++) {
      yield* iterArr(arr[i]);
    }
  } else {
    yield arr;
  }
}
var arr = ['a', ['b', 'c'], ['d', 'e']];
for (var x of iterArr(arr)) {
  console.log(x);
}
var arr = ['a', ['b', ['c', ['d', 'e']]]];
var gen = iterArr(arr);
arr = [...gen]; 
复制代码

参考答案

1682519215(1).png


拓展


众所周知,async 是目前最好的异步方案,是因为 async 写法非常清晰明了,但是能保证其异步的特性,早起没有 async 的时候社区里面就有使用自执行函数+生成器函数+Promise 实现了类 async 的效果,最终也是将这种思想成功的带进了 ES2017。

那么具体是怎么实现的呢,我们来逐步实现一下,首先是异步代码,我们先按照 async 的写法来看一下代码

function req1() {
  return new Promise((resolve) => {
    setTimeout(() => resolve(2), 1000)
  })
}
function req2() {
  return new Promise((resolve) => {
    setTimeout(() => resolve(22), 2000)
  })
}
async function exec() {
  const res1 = await req1();
  const res2 = await req2();
  return res1 + res2;
}
exec().then(console.log) // 22
复制代码

生成器版本是借助了一个第三方的库co来实现了自动执行,co 函数接收一个生成器函数,返回值是一个 promise 对象。

const co = require('co');
function* exec() {
  const res1 = yield req1()
  const res2 = yield req2()
  return res1 + res2
}
co(exec).then(console.log) // 22
复制代码

Generator 函数就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。

两种方法可以做到这一点:(1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权;(2)Promise 对象。将异步操作包装成 Promise 对象,用 then 方法交回执行权。

co 函数库其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个库。

相关文章
|
2天前
|
JavaScript 前端开发
JavaScript 闭包:让你更深入了解函数和作用域
JavaScript 闭包:让你更深入了解函数和作用域
|
2天前
|
JavaScript 前端开发 网络架构
函数柯里化:JavaScript中的高级技巧
函数柯里化:JavaScript中的高级技巧
|
2天前
|
JavaScript 前端开发
JavaScript的`apply`方法:函数的“应用”与“调用”
JavaScript的`apply`方法:函数的“应用”与“调用”
|
2天前
|
JavaScript 前端开发
JavaScript的`bind`方法:函数的“复制”与“定制”
JavaScript的`bind`方法:函数的“复制”与“定制”
|
2天前
|
JavaScript 前端开发
JavaScript的`call`方法:实现函数间的调用!
JavaScript的`call`方法:实现函数间的调用!
|
3天前
|
JavaScript 前端开发
在JavaScript中,函数原型(Function Prototype)是一个特殊的对象
【5月更文挑战第11天】JavaScript中的函数原型是一个特殊对象,它为所有函数实例提供共享的方法和属性。每个函数在创建时都有一个`prototype`属性,指向原型对象。利用原型,我们可以向所有实例添加方法和属性,实现继承。例如,我们定义一个`Person`函数,向其原型添加`greet`方法,然后创建实例`john`和`jane`,它们都能调用这个方法。尽管可以直接在原型上添加方法,但推荐在构造函数内部定义以封装数据和逻辑。
18 2
|
3天前
|
前端开发 JavaScript 数据处理
在JavaScript中,异步函数是指什么
【5月更文挑战第9天】JavaScript中的异步函数用于处理非立即完成的操作,如定时器、网络请求等。它们可通过回调函数、Promise或async/await来实现。示例展示了如何使用async/await模拟网络请求:定义异步函数fetchData返回Promise,在另一异步函数processData中使用await等待结果并处理。当fetchData的Promise解析时,data变量接收结果并继续执行后续代码。注意,调用异步函数不会阻塞执行,而是会在适当时间点继续。
11 0
|
3天前
|
自然语言处理 JavaScript 前端开发
在JavaScript中,this关键字的行为可能会因函数的调用方式而异
【5月更文挑战第9天】JavaScript中的`this`关键字行为取决于函数调用方式。在非严格模式下,直接调用函数时`this`指全局对象,严格模式下为`undefined`。作为对象方法调用时,`this`指向该对象。用`new`调用构造函数时,`this`指向新实例。通过`call`、`apply`、`bind`可手动设置`this`值。在回调和事件处理中,`this`可能不直观,箭头函数和绑定方法可帮助管理`this`的行为。
13 1
|
3天前
|
JavaScript 前端开发 网络架构
JavaScript中的箭头函数是一种新的函数表达形式
【5月更文挑战第9天】JavaScript的箭头函数以简洁语法简化函数定义,其特性包括:1) 不绑定自身this,继承上下文的this,适合回调和事件处理;2) 没有arguments对象,需用剩余参数语法访问参数;3) 不能用作构造函数,无new调用;4) 没有prototype属性,不支持基于原型的继承。箭头函数在特定场景下优化了this处理,但使用时要注意与普通函数的差异。
12 2
|
3天前
|
JavaScript 前端开发
js的一些内置函数
js的一些内置函数
8 1