技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTJS

ES6 新增的关键字 yield

所属馆藏
JavaScript
文件格式
Markdown
原始路径
JavaScript/17-ES6 新增的关键字 yield
本文目录12 个章节
  1. 一、yield 是做什么的?
  2. 二、生成器函数的语法
  3. 三、第一次调用 next() 不会给第一个 yield 传值
  4. 四、yield 可以接收和产生值
  5. 五、yield* 委托给另一个可迭代对象
  6. 六、生成器与迭代器协议
  7. 七、return() 和 throw()
  8. 八、用生成器实现惰性序列
  9. 九、yield 与异步编程的历史关系
  10. 十、异步生成器中的 yield
  11. 十一、常见误区
  12. 参考资料

ES6 新增的关键字 yield

Category(分类): JavaScript Status: 已更新

原文来源:CSDN《ES6 新增的关键字 yield》(原文件仅保留该链接;该站点对自动访问不稳定,当前正文以 ECMAScript 与 MDN 为准)

yield 随 ECMAScript 2015 的生成器(Generator)一起标准化。它不是普通函数中的“暂停关键字”,只有生成器函数或生成器方法的语法上下文可以直接使用。本文补齐原文缺失的概念、示例和现代说明。

一、yield 是做什么的?

yield 可以暂停生成器函数的执行,并把一个值交给调用方。生成器函数调用时不会立即执行函数体,而是返回一个生成器对象;调用生成器对象的 next(),函数才会运行到下一个 yieldreturn 或函数结束。

function* numbers() {
  yield 1
  yield 2
  return 3
}

const iterator = numbers()

console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log(iterator.next()) // { value: 3, done: true }
console.log(iterator.next()) // { value: undefined, done: true }

yield 返回的不是“单独一个值”,而是让 next() 返回一个符合迭代器协议的结果对象:

const result = numbers().next()
console.log(result.value) // 1
console.log(result.done) // false

二、生成器函数的语法

生成器函数声明使用 function*

function* createIterator() {
  yield 'a'
  yield 'b'
}

也可以使用生成器函数表达式、对象生成器方法或类生成器方法:

const generator = function* () {
  yield 1
}

const object = {
  *values() {
    yield 1
    yield 2
  }
}

class Collection {
  *values() {
    yield 3
    yield 4
  }
}

箭头函数没有对应的生成器语法,因此下面的代码是语法错误:

// const invalid = () => { yield 1 }

三、第一次调用 next() 不会给第一个 yield 传值

生成器开始时暂停在函数体入口。第一次调用 next(value) 时,传入的 value 没有可以接收它的 yield 表达式,因此通常会被忽略:

function* readValues() {
  const first = yield '请发送第一个值'
  const second = yield `收到 ${first},请发送第二个值`
  return second
}

const reader = readValues()

console.log(reader.next('第一次传入'))
// { value: '请发送第一个值', done: false }

console.log(reader.next('A'))
// { value: '收到 A,请发送第二个值', done: false }

console.log(reader.next('B'))
// { value: 'B', done: true }

规律是:next(value) 的参数会成为上一次暂停位置的 yield 表达式的结果。第一次 next() 只是启动生成器。

四、yield 可以接收和产生值

yield expression 会先把 expression 的结果交给调用者;恢复执行时,该 yield 表达式本身的结果由下一次 next(value) 的参数决定:

function* calculator() {
  const a = yield '请输入 a'
  const b = yield '请输入 b'
  return a + b
}

const calculation = calculator()
console.log(calculation.next()) // { value: '请输入 a', done: false }
console.log(calculation.next(2)) // { value: '请输入 b', done: false }
console.log(calculation.next(3)) // { value: 5, done: true }

这种双向通信能力是生成器区别于普通数组迭代器的重要地方。

五、yield* 委托给另一个可迭代对象

yield* 会把控制权委托给另一个可迭代对象,例如数组、字符串、生成器或自定义迭代器:

function* inner() {
  yield 2
  yield 3
}

function* outer() {
  yield 1
  yield* inner()
  yield 4
}

console.log([...outer()]) // [1, 2, 3, 4]

委托也会传递 next()throw()return() 的控制流,并且 yield* 表达式最终可以取得被委托生成器的返回值:

function* inner() {
  yield 'inner value'
  return 'inner result'
}

function* outer() {
  const result = yield* inner()
  return `outer got: ${result}`
}

const iterator = outer()
console.log(iterator.next()) // { value: 'inner value', done: false }
console.log(iterator.next()) // { value: 'outer got: inner result', done: true }

六、生成器与迭代器协议

生成器对象同时具有 next() 方法和 [Symbol.iterator]() 方法,因此它本身是一个可迭代的迭代器:

function* colors() {
  yield 'red'
  yield 'green'
  yield 'blue'
}

for (const color of colors()) {
  console.log(color)
}

const set = new Set(colors())
console.log(set) // Set(3) { 'red', 'green', 'blue' }

自定义对象只要实现 [Symbol.iterator],就可以被 for...of、展开语法和许多内置 API 消费:

const range = {
  start: 1,
  end: 3,
  *[Symbol.iterator]() {
    for (let value = this.start; value <= this.end; value += 1) {
      yield value
    }
  }
}

console.log([...range]) // [1, 2, 3]

生成器的惰性特点意味着值会按需生成,不需要一次性把全部结果放进数组;但如果生成器是无限的,不能直接用展开语法把它全部展开。

七、return()throw()

生成器对象的 return(value) 可以请求生成器结束,throw(error) 可以把错误注入当前暂停位置:

function* work() {
  try {
    yield 'working'
    yield 'more working'
  } finally {
    console.log('cleanup')
  }
}

const iterator = work()
console.log(iterator.next()) // { value: 'working', done: false }
console.log(iterator.return('finished'))
// cleanup
// { value: 'finished', done: true }

如果生成器内部用 try/catch 捕获了注入的错误,它可以继续产生值;否则错误会从 throw() 调用处抛出,生成器进入完成状态:

function* recoverable() {
  try {
    yield 1
  } catch (error) {
    yield `recovered: ${error.message}`
  }
}

const iterator = recoverable()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.throw(new Error('failed')))
// { value: 'recovered: failed', done: false }
console.log(iterator.next()) // { value: undefined, done: true }

八、用生成器实现惰性序列

function* powersOfTwo() {
  let value = 1
  while (true) {
    yield value
    value *= 2
  }
}

for (const value of powersOfTwo()) {
  if (value > 32) break
  console.log(value) // 1, 2, 4, 8, 16, 32
}

for...of 在提前 break 时会尝试关闭迭代器,因此生成器中的 finally 可用于清理资源。生成器适合惰性遍历、组合迭代器和实现自定义数据源,但不要把它当成线程或后台任务:生成器代码仍在调用它的 JavaScript 执行环境中运行。

九、yield 与异步编程的历史关系

async/await 普及之前,社区曾经用“生成器 + Promise + runner”把异步操作写成近似同步的形式:

function run(generatorFunction) {
  const iterator = generatorFunction()

  function settle(result) {
    if (result.done) return Promise.resolve(result.value)

    return Promise.resolve(result.value).then(
      value => advance('next', value),
      error => advance('throw', error)
    )
  }

  function advance(method, value) {
    let result
    try {
      result = iterator[method](value)
    } catch (error) {
      return Promise.reject(error)
    }
    return settle(result)
  }

  return advance('next')
}

run(function* () {
  const response = yield fetch('/api/data')
  if (!response.ok) throw new Error(`HTTP ${response.status}`)
  return response.json()
}).then(data => console.log(data))

这段代码是教学模型,并不是 async/await 的完整规范实现。现代代码通常直接使用:

async function loadData() {
  const response = await fetch('/api/data')
  if (!response.ok) throw new Error(`HTTP ${response.status}`)
  return response.json()
}

生成器和 async 函数的语义不同:生成器返回同步迭代器,通过 next() 恢复;异步函数返回 Promise,await 的恢复通过 Promise jobs 进行。生成器今天仍适合定义同步迭代器,也可以用于特定控制流库。

十、异步生成器中的 yield

异步生成器使用 async function*,返回异步迭代器,配合 for await...of 消费:

async function* streamValues() {
  yield 1
  await new Promise(resolve => setTimeout(resolve, 10))
  yield 2
}

async function consume() {
  for await (const value of streamValues()) {
    console.log(value)
  }
}

consume()

异步生成器的 next() 返回 Promise;它适合分页请求、流式读取和需要逐项异步生产的数据。yield 在异步生成器中产生的值会被包装为异步迭代结果。

十一、常见误区

  1. yield 不能写在普通函数或箭头函数中;
  2. 生成器函数调用时不会执行完整函数体,只会返回生成器对象;
  3. 第一次 next(value) 的参数不会传给第一个 yield
  4. yield 暂停的是当前生成器,不是整个 JavaScript 线程;
  5. 生成器不是 Promise,也不会自动等待异步操作;
  6. yield* 委托的是可迭代对象,不仅仅是另一个生成器;
  7. 无限生成器必须使用终止条件或手动 break,不能直接展开;
  8. 生成器函数不能用 new 构造实例。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS