【问题标题】:Using for await...of with synchronous iterables使用 for await...of 与同步迭代
【发布时间】:2020-06-27 13:52:01
【问题描述】:

MDN says for await...of 有两个用例:

for await...of 语句创建了一个循环遍历 async 可迭代对象以及同步可迭代对象,...

我之前知道前者:使用Symbol.asyncIterator 的异步迭代。但我现在对后者感兴趣:同步迭代。

以下代码迭代同步可迭代对象 - 一组承诺。它似乎阻碍了每个承诺的兑现。

async function asyncFunction() {
    try {
        const happy = new Promise((resolve)=>setTimeout(()=>resolve('happy'), 1000))
        const sad = new Promise((_,reject)=>setTimeout(()=>reject('sad')))
        const promises = [happy, sad]
        for await(const item of promises) {
            console.log(item)
        }
    } catch (err) {
        console.log(`an error occurred:`, err)
    }
}

asyncFunction() // "happy, an error occurred: sad" (printed in quick succession, after about 5 seconds)

根据下面显示的逻辑,该行为似乎类似于依次等待每个承诺。这个说法正确吗?

async function asyncFunction() {
    try {
        const happy = new Promise((resolve)=>setTimeout(()=>resolve('happy'), 1000))
        const sad = new Promise((_,reject)=>setTimeout(()=>reject('sad')))
        const promises = [happy, sad]
        for(let p of promises) {
            const item = await p
            console.log(item)
        }
    } catch (err) {
        console.log(`an error occurred:`, err)
    }
}

asyncFunction() // "happy, an error occurred: sad" (printed in quick succession, after about 5 seconds)

我之所以这么问,是因为这种代码模式有一个隐含的拒绝连接陷阱,Promise.allPromise.allSettled 避免了这种错误,而且这种语言会明确支持这种模式对我来说似乎很奇怪。

window.addEventListener('unhandledrejection', () => {
  console.log('unhandled rejection; `sad` was not being awaited at the time it rejected')
})

async function asyncFunction() {
    try {
        const happy = new Promise((resolve)=>setTimeout(()=>resolve('success'), 1000))
        const sad = new Promise((_,reject)=>setTimeout(()=>reject('failure')))
        const promises = [happy, sad]
        for(let p of promises) {
            const item = await p
            console.log(item)
        }
    } catch (err) {
        console.log(`an error occurred:`, err)
    }
}

asyncFunction() // "unhandled rejection; `sad` was not being awaited at the time it rejected" (after about zero seconds), and then "happy, an error occurred: sad" (printed in quick succession, after about 5 seconds)

【问题讨论】:

  • 您的问题到底是什么?您提供的示例似乎有效
  • 我对具有同步迭代的for await... of 的描述是否正确,如果正确,那么该模式是否会发出未处理的拒绝错误?
  • “它是否正确”不是问题。 “正确”就是你所说的。
  • 您能否通过代码演示您描述的未处理拒绝错误的发射?
  • 最终代码演示了它。正确在这种情况下具有明确的含义,因为我提供了代码来描述我认为它在做什么。如果行为符合我的代码,那么我的代码是正确的,否则我的理解是不正确的。此外,观察“正确”就是你所说的。 显然是不真实的。在这种情况下,正确具有明确定义的含义。

标签: javascript async-await


【解决方案1】:

是的,这很奇怪,你不应该这样做。不要迭代承诺数组,它会引导exactly to the unhandled-rejections problem you mentioned。 (另见this more specific explanation。)

那么为什么该语言支持这一点?继续使用草率的 Promise 语义。

你可以在this comment of the issue discussing this part of the proposal找到确切的推理:

我认为我们应该回到Symbol.iterator,因为我们目前的 Promise 语义都是关于允许将同步的东西用作 异步的东西。你可以称之为“草率”。它跟随 @groundwater's logic above, 但我只想更详细地说明相似之处。

.then 的“链接”语义都是关于这个的。你可以返回一个 来自.then 的承诺或标量值;这都一样。你打电话 Promise.resolve 不是在 Promise 中包装一些东西,而是投射 承诺的东西——当你有的时候得到一个异步值 某事或其他。

asyncawait 的语义也都是关于马虎的。 您可以在异步函数中的任何非 Promise 表达式上拍 await 一切正常,完全一样,除了你屈服 控制到作业队列。同样,您可以“防守”输入async 随心所欲,只要你await结果。如果你有 一个返回 Promise 的函数——无论如何!你可以把它变成 async 函数,并且从用户的角度来看,没有任何变化(甚至 如果从技术上讲,你得到一个不同的 Promise 对象)。

异步迭代器和生成器应该以相同的方式工作。就像你一样 可以等待一个意外地不是 Promise 的值,一个合理的值 用户希望能够yield* async 中的同步迭代器 发电机。 for await 循环应该同样“正常工作”,如果用户 以这种方式防御性地标记一个循环,认为他们可能是 获取异步迭代器。

我认为打破所有这些相似之处将是一件大事。它 会使异步迭代器更不符合人体工程学。让我们接下来讨论这个 时间异步生成器/迭代器出现在 TC39 的议程中。

【讨论】:

  • 谢谢。是发出事件,还是实际上是某种其他类型的错误?我问是因为我认为事件是 WebAPI 的一部分。在规范的其他部分是否以类似的方式使用事件发射?
  • @52d6c6af 你指的是unhandledrejection事件吗?
  • 是的。只是为了拦截我使用的“错误”window.addEventListener('unhandledrejection',... 简而言之:这是我能想到的唯一一个例子,JavaScript 发出这种错误。然而,我几乎可以肯定地认为这是错误的。最后:除了在控制台中出现不需要的错误消息之外,这个“错误”的发射真的很重要吗?
  • @52d6c6af 请参阅herethere,了解如何在 ECMAScript 和 Web API 规范的共同努力中指定。不,事件并不重要,当你得到这个时已经太晚了。 Afaics,它只用于监控客户端错误。
  • 如果这并不重要,建议仍然是“不要迭代承诺数组”,还是“注意在某些情况下这不会表现出快速失败的行为”?
【解决方案2】:

sad 承诺不是当它失败时被awaited - 该代码需要在开始等待happy 之前完成等待sadsad 承诺在 happy 解决之前失败。 (Promise.all 是更适合此用例的工具)

【讨论】:

  • 我知道。因此我的问题。如果Promise.all 是一个更好的解决方案,为什么该语言会迎合这种语法? for await...of 可以很容易地实现为简单地枚举异步迭代。但是他们迎合了它来枚举同步迭代(但有一个(似乎?)陷阱)。为什么?
  • 啊,我误会了。我们在问为什么for await ... of 接受同步迭代吗?我想支持有条件地返回同步项的异步生成器。
  • 是的,尤其是考虑到它似乎会引入拒绝接线陷阱。
  • 在我看来,更普遍的陷阱在于创建一个承诺,而不是立即等待它。不幸的是,这个陷阱通常也是一个非常有用的功能。
猜你喜欢
  • 2021-07-10
  • 1970-01-01
  • 1970-01-01
  • 2017-02-08
  • 2018-09-04
  • 2014-09-28
  • 2017-06-05
相关资源
最近更新 更多