【问题标题】:When are throw(error) and return(value) useful in javascript iterators?throw(error) 和 return(value) 什么时候在 javascript 迭代器中有用?
【发布时间】:2019-12-10 17:41:34
【问题描述】:

在 javascript 中,允许迭代器具有 throw(error)return(value) 方法。 return(value) 让迭代器有机会看到value,并有望返回{value: value, done: true}throw(error) 使迭代器有机会查看并可能捕获错误。如果错误被捕获,throw 应该返回下一个值。如果异常没有被捕获,它应该等价于return(undefined)。以下是这些机制的实际应用示例:

function* test() {
    try {
    yield 1;
  } catch(e) {}
  const fromConsumer = yield 2;
  yield fromConsumer;
  // I know no way to access the value passed to return in a generator
}

const iter = test()[Symbol.iterator]();
console.log(iter.next());
console.log(iter.throw(new Error('catch me')));
console.log(iter.next(9));
console.log(iter.next());
console.log(iter.return(0));

(与jsfiddle 相同)

我的问题是:为什么?有没有人有针对迭代器的这种控制 API 反转的合理用例?在什么情况下,迭代器处理在使用它时发生的错误实际上是有意义的?鉴于肯定会结束迭代器并且不能进一步影响迭代器 API 的行为,您何时希望将值传递给 return?

我会说我知道的一个用例是 redux-saga,他们在其中利用了迭代器的控制反转 API,似乎是穷人的 async/await。如果有人熟悉该工具的设计或使用,他们的选择还有其他好处吗?

【问题讨论】:

  • 我没有时间就这个问题发表一个正确的答案,并且怀疑有些人会说这个问题对于 SO 来说过于开放或基于意见(什么是“可辩护的”用例) ?),但是:查看协程。虽然允许迭代器拥有returnthrow,但真正有用的是生成器。使用生成器,您可以构建协程。协程不仅仅是“穷人的async/await”。
  • “通常,这些方法的调用者应该在调用它们之前检查它们的存在。某些 ECMAScript 语言功能,包括for-ofyield*,以及数组解构在执行存在检查后调用这些方法. 大多数接受 Iterable 对象作为参数的 ECMAScript 库函数也有条件地调用它们。”(来源:tc39.es/ecma262/#table-54
  • 关于 redux saga 问题板的讨论也颇具启发性。他们指出,与 async/await 不同,可以进行同步执行(正如另一个答案中提到的,提前返回)。

标签: javascript iterator redux-saga


【解决方案1】:

首先,并不是所有的迭代器都有返回和抛出。迭代器唯一需要的是next。但是生成器函数创建的迭代器确实有returnthrow

我会说我知道的一个用例是 redux-saga,他们在其中利用了迭代器的控制反转 API,似乎是穷人的 async/await。

我会说这是倒退。生成器不是 async/await 的糟糕版本; async/await 是对生成器的狭义使用。事实上,async/await 的旧等价物实际上是使用生成器实现的,如 co 之类的库中所见。现在 async/await 是核心语言的一部分,我不知道底层的 c++ 代码是否真的在使用生成器,但从历史和概念上讲,它是生成器的产物,与解决承诺的狭隘情况有关。

使用像 redux-saga 这样的库,不仅可以获得类似于 await 的语法,而且还支持更复杂的事情,例如任务取消,这是 async/await 无法做到的。任务取消取决于return 方法的工作。还支持错误处理,这可以通过throw 方法实现。

所以生成器是一个非常广泛的工具,除其他外,它可用于对异步行为进行建模。因为它们的范围很广,所以它们带有一组功能,这些功能不仅可以对迭代进行建模。我认为生成器不仅仅是一种迭代事物的方式,而是在两段代码之间进行双向对话。您可能想要传达的内容包括“有问题”或“我们已经完成了”,这就是 throw 和 return 的用途。

鉴于肯定会结束迭代器并且不能进一步影响迭代器 API 的行为,您何时希望将值传递给 return?

其实还是可以影响的。如果生成器使用 try/finally,.return() 会将生成器发送到 finally 块,在那里它可以运行尽可能多的代码。生成器甚至可以在这个 finally 块中产生,具有它在 finally 块之前的所有相同行为。继续以redux saga为例,使用generators的这个特性来支持在取消的情况下回滚

function* exampleSaga() {
  try {
    // start working on something, with at least one yield
  } finally {
    if (yield cancelled()) {
      // roll back
    }
  }
}

【讨论】:

  • 这只是返回有用,而不是返回值。
  • 你是对的,我只是表明它不确定会结束迭代器并且不会阻止进一步的影响。如果您希望值重要,则必须编写不使用生成器函数的自定义迭代器,因为生成器函数会忽略传递给 return 的值。
【解决方案2】:

这是我到目前为止的想法:

将代码编写为由协程操作的生成器比使用 async/await 与远程代码交互具有以下优势:

  1. 协同程序可以在生成器上使用三种潜在的反馈路径:yieldthrowreturn,而不仅仅是 resolverejectreturn 在这种情况下表示 on option not available with vanilla promises,即取消。

  2. 与 async/await 不同,不要求传输控制流会导致异步。这允许协程选择控制是同步还是异步继续,并允许它检查生成的值并在它们不是承诺时立即使用它们。

最后,关于生成器何时适合从消费者那里捕获错误,答案是:当消费者是一个协程,在执行生成器指示它做的事情时遇到错误,在这种情况下,生成器,作为导致错误的动作的来源,也应该了解如何处理它。

【讨论】:

    猜你喜欢
    • 2012-02-27
    • 2018-02-27
    • 2016-01-11
    • 1970-01-01
    • 2015-10-28
    • 1970-01-01
    • 2020-12-20
    相关资源
    最近更新 更多