使用redux-saga时,为什么yield语句是执行的值而不是undefined?

When using redux-saga, why yield statement is the value of the execution rather than undefined?

假设我们有以下生成器函数:

function* testGenerator() {

  const result = yield Promise.resolve('foobar')<del>.then(res => res);</del>

  console.log(result);

}

如果我 运行 这个生成器带有以下行,它会记录 undefined

const test = testGenerator();
test.next();
test.next();

但在 saga 中,这样的行将记录 foobar。我只是好奇这背后的机制是什么(将 yield 的结果赋值给一个变量)

编辑:

长话短说:

MDN function* reference

Calling the next() method with an argument will resume the generator function execution, replacing the yield statement where execution was paused with the argument from next()

基本上,要将其记录到日志 "foobar",只需将第一个 yield 的值传递给第二个 next,这样当它恢复时,yield 语句将替换为值:

function* testGenerator() {
  const result = yield Promise.resolve('foobar');
  console.log(result);
}

const test = testGenerator();
test.next().value.then(r => test.next(r))

代码审查

首先,摆脱这个;它什么都不做

const result = yield Promise.resolve('foobar')<del>.then(res => res)</del>;

你的发电机工作正常

If I run this generator with the following line, it logs undefined

不,不是。它会记录一个 {next, value} 每次

function* testGenerator() {
  const result = yield Promise.resolve('foobar');
  console.log(result);
}

let test = testGenerator()

console.log(test.next())
// { value: Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 1}, done: false }

console.log(test.next())
// { value: undefined, done: true }

虽然记录 Promises 很棘手。不信你看这个

function* testGenerator() {
  const result = yield Promise.resolve('foobar');
  console.log(result);
}

let test = testGenerator()

test.next().value.then(console.log)
// "foobar"


协程

But in saga, such lines will log foobar. I'm just curious what's the mechanism behind this (assigning the result of yield to a variable)

Redux Saga 的构建是为了单步执行生成器并处理各种效果。 Promise 只是您可以 yield 的众多事物之一。

这是一个简单的协程函数示例,它采用生成器实例并期望在最后一个结果之前产生 Promise。注意双向数据流。

  • yield 从生成器中发送数据
  • gen.next(x) 将下一个值发送到生成器

所以我们正在利用生成器的这种能力来发送承诺 out,并将 Promise 的 resolved 值发回 in,它允许将已解析的 Promise 的值直接分配给变量

const coro = gen => {
  const next = x => {
    const {value, done} = gen.next(x)
    if (done)
      return value
    else
      return value.then(next)
  }
  return next()
}

function* testGenerator () {
  const x = yield Promise.resolve(1)
  console.log(x) // 1

  const y = yield Promise.resolve(2)
  console.log(y) // 2

  const z = yield Promise.resolve(3)
  console.log(z) // 3

  return x + y + z
}

coro(testGenerator()).then(console.log)
// "6"


async & await

现在您明白了这一点,您就完全了解了提议的 async/await 是如何工作的——下一个片段的功能可能会有所不同,具体取决于您的浏览器的支持。

变化:

  • 我们不再需要 coro 助手
  • 使用 async 关键字而不是生成器 function*
  • 使用 await 关键字代替 yield

行为相同。

  • 使用await发送一个Promise
  • Promise 的解析值作为 return 调用 await 的值发回
  • async 隐式地运行 return Promise,因此链接一个 .then 调用以获取最终值

const testRoutine = async () => {
  const x = await Promise.resolve(1)
  console.log(x) // 1

  const y = await Promise.resolve(2)
  console.log(y) // 2

  const z = await Promise.resolve(3)
  console.log(z) // 3

  return x + y + z
}

testRoutine().then(console.log)
// "6"


自制协程

您不应该推出自己的协程,因为有很多东西需要注意。例如,如果生成器 throw 是一个错误或 yield 是一个被拒绝的 Promise,上面的实现只会吞下它。

为了演示目的我将向您展示如何进一步确定它,但如果您有兴趣涵盖所有基础知识,您应该阅读Redux Saga 源码,或者tj/co

等其他协程库的源码

const coro = gen => {
  return new Promise((resolve, reject) => {
    const next = x => {
      try {
        let {value, done} = gen.next(x)
        return done ? resolve(value) : value.then(next, reject)
      }
      catch (err) {
        reject(err)
      }
    }
    next()
  })
}

function* noYield() { return 5 }

function* throwsUp() { throw Error("OOPS") }

function* yieldReject() { yield Promise.reject('NO') }

function* normal() { let x = yield Promise.resolve(16); return x * x; }

coro(noYield())
  .then(console.log, console.error) // 5

coro(throwsUp())
  .then(console.log, console.error) // [Error: OOPS]
  
coro(yieldReject())
  .then(console.log, console.error) // "NO"
  
coro(normal())
  .then(console.log, console.error) // 256