【问题标题】:ES8 Immediately invoked async function expressionES8 立即调用异步函数表达式
【发布时间】:2017-04-06 07:44:48
【问题描述】:

我没有看到这些构造被大量使用,但我发现自己编写它们是为了在通常不会返回承诺的函数中使用 async / await,例如

chan.consume(queue, (msg) => {
  this.pendingMsgs++; // executed immediately
  (async () => {
    await this.handleMessage(msg);
    this.pendingMsgs--;
    if (cancelled && this.pendingMsgs === 0) {
       await chan.close();
       await this.amqpConnectionPool.release(conn);
    } 
  })();
});

相对于

chan.consume(queue, async (msg) => { // external lib does not expect a return value from this callback
  this.pendingMsgs++;  // executed in promise context(?)
  await this.handleMessage(msg);
  this.pendingMsgs--;
    if (cancelled && this.pendingMsgs === 0) {
       await chan.close();
       await this.amqpConnectionPool.release(conn);
    }
});

chan.consume(queue, (msg) => {
  this.pendingMsgs++;  // no await - excess function decls & nesting
  this.handleMessage(msg).then(() => {
    this.pendingMsgs--;
    if (cancelled && this.pendingMsgs === 0) {
       chan.close().then(() => {
         this.amqpConnectionPool.release(conn);
       });
    }
  });
});

这是“一件事”吗?这里有我应该注意的陷阱吗? 在这种情况下使用 async / await 有什么不足之处?

【问题讨论】:

  • @DrewR 将返回值添加到当前不返回任何内容的函数不会违反任何替换原则。那么,为什么还要费心将所有内容都包装在另一层函数和缩进中呢?
  • 那么,第一个代码示例实现了第二个没有实现的什么(除了引入一个新功能)?我的意思是,通常你可以在 IIFE 块中包装代码块,但通常人们根本没有理由不这样做。例如,有时是为了保护本地命名空间。这是做什么的?
  • @Pointy 为了使用'await'关键字,关键字出现的函数必须用'async'注释。 'async' 注释是'returns a promise' 的语法糖。由于调用回调的库没有预料到这一点,因此没有机会处理 Promise 可能抛出的任何错误(编辑 - 这是错误的,尝试...全部捕获)。此外,在这个特定函数中,需要立即增加 this.pendingMsgs(不要推迟到事件循环的某些后续迭代)——将回调声明为异步并不提供这样做的机会。
  • 关于异常的要点是需要考虑的,但是在第二个示例中,pendingMsgs 的增量将与第一个示例同时发生。将async 回调传递给的函数仍将被立即调用,并且该增量发生在任何异步操作开始之前。
  • @Pointy 考虑一下我可以将异步 fn 的全部内容包装在 try ... catch 中,如果我想在这种情况下处理错误,请确保不要重新抛出。所以那里没有问题。所以上面的例子在功能上等价于 when this.pendingMsgs++;被执行?我担心这里调用回调的问题,增量和后续代码被延迟,来自较早调用的较早承诺完成,递减pendingMsgs,观察它为零并在实际上有另一条消息时开始关闭资源马上处理。

标签: javascript async-await ecmascript-2017


【解决方案1】:
(async () => {
        await func();
    })();

【讨论】:

    【解决方案2】:

    这是“一件事”吗?

    是的。它时不时出现,例如here。它们被称为 IIAFE :-)
    如果你想把重点放在箭头上,你也可以称它们为 IIAAF。

    这里有什么我应该注意的陷阱吗?

    每当您调用一个返回承诺的函数并且不将结果返回到其他地方时,您自己对承诺负责 - 这意味着您必须处理来自它的错误。所以模式通常应该看起来像

    (async () => {
        …
    })().catch(err => {
        console.error(err);
    });
    

    如果您不想担心未处理的拒绝事件。

    在这种情况下使用async/await 有什么好处?

    then 版本相比,不多。但是,您说“外部库不期望此回调的返回值”,这可能暗示该库与异步回调不兼容,因此请注意您何时执行的操作。它还可能取决于从回调同步抛出的异常,所以这完全取决于库在这里的期望(如果没有期望,将来是否会改变)。您不希望将来出现不兼容性,以防库开始特别处理承诺返回值。

    但是,我仍然推荐第二种模式,直接将async 函数直接作为回调传递,因为它具有更好的可读性。如果您想避免向库返回承诺,请创建一个包装回调的辅助函数:

    function toVoid(fn) {
        return (...args) => void fn(...args);
    }
    function promiseToVoid(fn) {
        return (...args) => void fn(...args).catch(console.error);
    }
    

    你可以这样使用:

    chan.consume(queue, toVoid(async (msg) => {
         … // use `await` freely
    }));
    

    【讨论】:

    • 我喜欢包装函数的想法。谢谢。
    • 对未处理的承诺拒绝的注释非常赞赏——这是一个很好的答案。异步口袋函数现在开始出现在很多文档中,很多人都忽略了将一个口袋函数插入非异步函数时可能发生的错误。我还说值得指出的是,.catch() 只有在您不使用await 调用口袋函数时才需要。即在不添加.catch() 的情况下运行await (async()=>{})(); 很好。但是,如果您将await 排除在外,那么您必须记住在最后链接.catch()
    • async 函数允许你在其中说try { } catch (error) { } 时,你为什么还要使用.catch()
    • @chbchb55 主要是因为try/catch 需要额外的缩进级别,我不喜欢这样。特别是当catch 是事后的想法并且与实际控制流无关时。此外,在async function 中,try 块之外可能会发生异常 - 使用内置错误处理我们可以处理它们。最后但并非最不重要的一点是.catch() 更短:-)
    猜你喜欢
    • 2014-11-26
    • 2018-01-07
    • 2012-12-28
    • 1970-01-01
    • 1970-01-01
    • 2016-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多