【问题标题】:Is leaving useless synchronous code under Async/Await an anti-pattern?在 Async/Await 下留下无用的同步代码是一种反模式吗?
【发布时间】:2021-04-05 05:32:50
【问题描述】:

据我了解,await 背后的意义在于“等待”对承诺的已解决值采取行动,直到它作为微任务遇到,正如 Jake Archibald 解释的 here

This video by LLJS 表明 async-await 本质上是生成器运行器/解释器函数的语法糖,它产生它等待的位置并将承诺的确定值传递给 .next() 方法。这意味着当等待发生时,运行器对.next() 的执行将作为微任务排队。

实际上,await 下的所有代码只会在下一个微任务检查点执行。如果不需要等待的承诺值的代码位于其下方,这可能是一个问题,这正是 Async IIFE 的问题。

async function ping() {
  for (let i = 0; i < 5; i++) {
    let result = await Promise.resolve("ping");
    console.log(result);
  }

  console.log("Why am I even here?");
}
    
async function pong() {
  for (let i = 0; i < 5; i++) {
    let result = await Promise.resolve("pong");
    console.log(result);
  }

  console.log("I have nothing to do with any of this");
}
    
console.log("Let the games begin!");
ping();
pong();
console.log("Placeholder for code that is not related to ping pong");

在此示例中,首先记录外部日志作为运行脚本任务的一部分,然后按照它们在微任务队列中排队的顺序记录已解决的承诺的值。在整个过程中,for 循环下面留下的日志与循环无关,会不必要地暂停,直到各自函数体中的最后一个微任务出队列。

这正是我们使用 async 函数作为 IIFE 时发生的情况。如果您在await 下有旨在同步执行的代码,则它必须不必要地等待,直到它上面的所有等待都已从微任务队列中检出。

如果有人盲目地将他们的整个快速路由包装在async 函数中,我可以看到这是一个问题,在那里他们将不必要地await 解决某些承诺,如数据库操作、发送电子邮件、读取文件等...,那么为什么人们仍然这样做呢?

app.post('/forgotPwd', async (req, res) => {
  const {email, username} = req.body;

  if (!email) {
    res.status(400).json({error: "No username entered"});
    return;
  }

  if (!username) {
    res.status(400).json({error: "No email entered"});
    return;
  }

  const db = client.db();
  
  const user = await db.collection("Users").findOne({username: username, "userInfo.email": email});

  if (!user) {
    res.status(400).json({error: "Account not found"});
    return;
  }

  const authToken = await getAuthToken({id: user._id.toHexString()}, "15m");

  // Would probably send a more verbose email
  await sgMail.send({
    from: process.env.EMAIL,
    to: email,
    subject: 'Forgot Password',
    text: `Use this url to reset your password: http://localhost:5000/confirmation/passConf/${authToken}`,
  });

  res.json({error: ""});
});

【问题讨论】:

  • 次要术语说明:您的意思是解决(拒绝或履行)或履行,而不是解决。一个承诺可以解决,但仍处于待处理状态。
  • 我不会称该代码为无用,如果作者的意思是立即执行该部分,我只会称它为错误。跨度>
  • 如果有人盲目地将他们的整个快速路由包装在异步函数中” - 你能举一个具体的例子吗?我不确定你指的是什么。
  • 感谢您添加示例,但您认为该代码的哪一部分与异步调用无关,应该立即完成?
  • @Clever7- 当然你需要等待!它可能会拒绝,在这种情况下需要发送错误响应,因此您无法在数据库事务完成之前发送响应。 (诚​​然,您展示的代码中缺少正确的错误处理,但这不是借口)。

标签: javascript async-await event-loop


【解决方案1】:

使用 async/await 的目的是让异步代码看起来是同步的,因为它更易于阅读。事实上,它一个隐藏回调地狱的语法糖。您无需处理回调即可处理异步操作。

在您的情况下,如果您认为for 循环之后的代码与等待的操作无关,则不应将其放在await 之后。或者您应该重构代码,使其不使用await(回调)。

至于人们为什么这样做的问题。那么,你能说出为什么人们使用.map() 来代替.forEach() 吗?或者你能说出他们为什么不处理异常吗?他们可能并不完全理解它,或者(正如 T.J. Crowder 提到的)他们确实希望代码在等待的操作之后运行。就这么简单。

【讨论】:

    【解决方案2】:

    如果您希望 async 函数中的某些内容同步运行,请确保它在函数中的第一个 await 之前。

    那么为什么人们仍然这样做呢?

    这对于 SO 来说可能是一个离题的问题,因为它在很大程度上需要基于意见的答案,但很可能是 A) 因为他们希望该代码运行到上面的代码已经完成,或者 B) 因为他们不理解 async 函数。

    【讨论】:

      猜你喜欢
      • 2020-04-20
      • 2017-08-19
      • 1970-01-01
      • 1970-01-01
      • 2014-09-28
      • 1970-01-01
      • 2019-10-12
      相关资源
      最近更新 更多