【问题标题】:Async function not behaving as expected in Nodejs异步函数在 Nodejs 中的行为不符合预期
【发布时间】:2019-07-23 00:41:41
【问题描述】:

我有这个简单的代码:

(async ()=>{
    const five = await printHello()
    console.log(`five: ${five}`);
})()

async function printHello(){
    console.log('Hello World')
    return 5;
}

console.log("App started");

我的期望是它应该打印出来:

App started    
Hello World
five: 5

因为被执行的匿名函数被标记为异步。

但它会打印,即使经过多次试验:

Hello World
App started
five: 5

谁能解释一下为什么会这样?

【问题讨论】:

  • 这可能是因为代码是从上到下执行的,所以在“App started”之前会显示“Hello World”消息。
  • @Edric 是的,但是当您将其标记为异步时,调用会转到下一行,并且异步函数会在事件循环的下一个滴答声中执行。

标签: javascript node.js async-await es6-promise


【解决方案1】:

首先,您拨打printHello()

然后你打电话给console.log('Hello World') 记录。

然后return 5; 解决了承诺。

然后你 const five = await ... 等待上面返回的承诺。这是一个微任务,因此它会排队并且异步函数进入睡眠状态。

IIFE 完成运行,因此外部函数继续以 console.log("App started"); 记录应用程序启动。

然后外部函数完成,所以微任务被拉出队列并且异步函数被唤醒。

promise 中的解析值被分配,console.log(`five: ${five}`); 记录它。

请参阅Timing from MDN 了解更多信息。

【讨论】:

  • 匿名立即执行的异步函数怎么样?它应该创建微任务并排队等待下一个滴答声。 “应用已启动”是针对当前刻度的。
  • 纯形式的 Promise 会表现得像这样,但 async 函数会暂停一切。所以最后一行等待 IIFE 完成。
  • @user3769778 — 调用异步函数不会创建微任务。是then和朋友。
【解决方案2】:

async 函数返回一个承诺,但async 函数内部的同步代码会立即执行(即同步)。 await 语句暂停代码执行,直到给它的承诺被解决或拒绝。

我总是将async 函数行为与Promiseexecutor param 行为进行比较。 executor 函数被立即调用,即同步调用。

【讨论】:

    【解决方案3】:

    您的代码不是异步的。这就是问题所在。您需要返回一个始终异步的承诺。

    const users = [
      {
        id: 0,
        name: 'foo',
      },
      {
        id: 1,
        name: 'bar',
      }
    ];
    
    function getUserById(id) {
      return new Promise((resolve, reject) => {
        setTimeout(function() {
          resolve(users.filter(user => user.id === id)[0]);
        }, 1000);
      });
    }
    
    async function getUser(id) {
      let name;
    
      await getUserById(id).then(function(user) {
        name = user ? user.name : null;
      });
    
      console.log(name);
    }
    
    getUser(0);
    console.log('console');
    

    【讨论】:

      【解决方案4】:

      您的代码没有执行任何异步操作。 printHello 方法同步执行。 使用 async 为函数添加前缀只需确保它返回一个 Promise。您可以将其替换为回调,它的工作方式相同,因为回调将立即返回,而无需等待事件循环的下一个滴答声。

      因此,如果您希望代码按照您希望的方式运行,则必须使用 setTimeout 或 setImmediate 调用将其包装起来。

      async function printHello() {
            setImmediate(() => {
              console.log('Hello World');
              return 5;
            });
        }
      

      【讨论】:

      • 如果我在 printHello 中有一个非常大的计算并且我希望它不会挂起我的事件循环,那么我必须使用哪些选项使其异步?我认为将任何函数标记为异步都会使其异步并且传递将转到下一行。
      • 将它包装在 setImmediate 或 process.nextTick 中会使其看起来是异步的,并且不会立即阻止执行。但是在子进程中这样做会更好(nodejs.org/api/child_process.html
      • 虽然代码没有做任何异步操作,但它的行为仍然是异步的,因为 Promise(也是从异步函数返回的)保证了异步解析。你是对的,不过,卸载到另一个进程似乎是正确的方法。
      【解决方案5】:

      所有人都给出了很多有用的答案,但我需要为此写一个答案来帮助那些想出与我相似的心态的人:仅仅通过将函数标记为异步 [或将其包装在 Promise 中] 不会使你的 CPU 密集型代码在 nodejs 中作为异步函数运行。

      在这个问题之前我的理解是,我们可以把 async 放在一个函数之前,然后把所有繁重的任务都放在它上面。 Aysnc 将介绍如何让 nodejs 事件循环知道一旦完成 [返回]。而且我曾经认为它通过产生线程来完成所有这些事情。

      但是这种理解是不正确的。如果您要编写代码,Node 是单线程的。永远不要把任何繁重的任务放在 nodejs 上。

      考虑一下这个快递应用:

      const express = require('express')
      
      const app = express()
      
      app.use('/name/:name',(req,res)=>{
          if(req.params['name']==='John'){
              res.end(timeConsumingFunct(2000).toString())
          }else{
              res.end(timeConsumingFunct(10000).toString())
          }
      })
      
      
      function timeConsumingFunct(ms) {//simulating CPU intensive work
          var start = Date.now(),
              now = start;
          while (now - start < ms) {
            now = Date.now();
          }
          return ms/1000;
      }
      
      app.listen(3000,()=>{
          console.log('Server started at : http://localhost:3000')
      })
      

      现在,如果您在浏览器中先访问:http://localhost:3000/name/Tom,然后再访问 http://localhost:3000/name/John,Mr.John 至少需要等待 12 秒。 这是因为 Node js 是单线程的,无论您使用 async 还是 Promise 来包装您的 timeConsumingFunct,它都会挂起整个流程。

      使其真正异步的唯一方法是使用子进程或工作 api 或用 C++ 编写。

      无论你作为 JS 编写什么总是同步的,它会挂起所有请求,在你认为你可以在 nodejs 中做任何事情之前知道这一点。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-05
        • 1970-01-01
        • 2010-10-23
        • 1970-01-01
        • 2018-04-26
        • 1970-01-01
        • 2017-10-23
        • 1970-01-01
        相关资源
        最近更新 更多