【问题标题】:Why is this async function not behaving asynchronously?为什么这个异步函数没有异步运行?
【发布时间】:2021-01-15 21:22:27
【问题描述】:

我正在学习 Javascript。我不习惯异步编程,所以在这里很难。这是我的代码:

var x = 0;

async function increment(x) {
  ++x;
  for (var i = 0; i < 999999999; i = i + 1);
  console.log('value of x is: ' + x);
};

increment(x);

console.log('Out of the function');

期望:'Out of the function' 会立即打印出来。 “x 的值...”需要一些时间。
现实:“x 的值...”在延迟后首先打印。然后打印'Out of the function'。

我的问题是,为什么不异步调用 increment()?为什么会阻塞最后一个console.log?

【问题讨论】:

  • 大部分答案似乎都是关于解决方案的。但我更期待一些解释为什么我的期望不正确。我想每当我使用 async 时,该函数都不会阻止下一次同步执行——无论是否使用 await。我只是不想让增量函数阻止最后一个 console.log()
  • 如果你把 async 放在没有 await 的地方,这个函数会表现得像一个普通的(同步)函数。因此,increment(x) 将阻止最后一个 console.log()。我在我的回答中添加了指向 MDN 的链接,可能有助于更好地理解。
  • @FarhanFuad 查看我的答案,尤其是 Bob-Alice 部分。

标签: javascript node.js asynchronous async-await synchronization


【解决方案1】:

TL;DR:您必须在 async 函数中 await something 使其异步。

var x = 0;

async function increment(x) {
  await null;  // <-- ADD THIS LINE
  ++x;
  for (var i = 0; i < 10; i = i + 1);
  console.log('value of x is: ' + x);
};

increment(x);

console.log('Out of the function');

加长版:

首先让我们澄清一件事:JS 运行时是一个基于事件循环的单线程运行时,这意味着没有并发/并行代码执行(除非你使用Workerchild_process,这是另一回事主题)。

现在到异步部分。 async-await 只是一个方便的语法糖,它在底层使用了PromiseGenerator,这反过来又调用了 platfrom 提供的事件循环调度 API。在 Node.js 中,API 是 process.nextTick,而在浏览器中是 setImmediate

通过这些 API,作业被调度,缓存在 微任务队列 中,空闲时事件循环会点击并执行这些任务。因此,异步实际上只是调度。


有了这些知识,让我们回到你的案例。

async function 关键字做了两件事:

  1. 它强制函数返回一个Promise
  2. 它允许你在函数体内使用await关键字

当您使用await 关键字时,实际的调度部分就会发生。它的作用是将计划任务发送到微任务队列,这将始终是对Promise.then() 调用的回调(在上面的示例中,我发送null,它将自动包装为Promise.resolve(null) ),然后暂停当前函数的执行,并等到该 promise 解决后再继续执行其余函数。

因此,如果您不使用 await 关键字涉及事件循环调度程序,则执行顺序将保持正常。从而解释你的情况。


如果你明白async 函数只是Promise 周围的语法糖,那么下一个常见的误解是new Promise(callback) 的执行顺序。

案例一:

new Promise((resolve) => {
  console.log('Bob comes first!')
  resolve(null)
})

console.log('Alice comes first!')

:谁先来?
:鲍勃先来。

案例 2:

new Promise((resolve) => {
  resolve(null)
}).then(() => console.log('Bob comes first!'))


console.log('Alice comes first!')

:谁先来?
:这次爱丽丝先来。

案例 1 对应于您的代码。 async 将函数包装到 Promise 的回调中,但如果您不这样做 .then(),则该回调会立即按正常顺序执行。

案例 2 对应于我的 await null 示例。它将函数体分成两部分,一个在new Promise(callback) 内部,其余在promise.then(callback) 内部。并且后者的执行顺序是延迟的。

【讨论】:

    【解决方案2】:

    async 函数没有await,所以函数会同步运行。如果没有提供 await,javascript 不会抛出任何错误。

    来自MDN

    异步函数的主体可以被认为是被零分割 或更多等待表达式。顶级代码,直至并包括 第一个等待表达式(如果有的话),同步运行。在 这样,一个没有等待表达式的异步函数将运行 同步

    将其转换为如下所示的内容,您将看到预期的行为:

     var x = 0;
    async function increment(x) {
      ++x;
      await setTimeout(function() {
        for (var i = 0; i < 999999999; i = i + 1);
      });
      console.log('value of x is: ' + x);
    };
    
    increment(x);
    
    console.log('Out of the function');

    【讨论】:

    • @adiga,这是传达概念的一个例子,我相信它可以达到目的。
    • @adiga,你的意思是如果我删除 await,它仍然会在“x 的值”之前记录“Out of the function”?
    • 哦,别担心,伙计。我以为我在概念上错过了一些东西。
    • 这是一个误导性的答案。您使用setTimeout 接入事件循环调度API,这确实会给您异步代码执行顺序,但这与async-await 机制无关。将两者放在一起非常令人困惑。
    • 您可以await whateverAsLongAsItsAnExpression 使此示例正常运行。
    【解决方案3】:

    因为我想 JavaScript 不是多线程的。

    您可以尝试使用 JQuery 方法(也许)或了解 Web Workers 我发现谷歌搜索的第一个资源: https://medium.com/techtrument/multithreading-javascript-46156179cf9a

    【讨论】:

      【解决方案4】:

      你应该使用等待。来自官方 JS 文档:“关键字 await 使 JavaScript 等到该承诺完成并返回其结果。”

      此外,如果它看起来有点奇怪,但您可以通过以下方式实现您的结果:

      const x = 0;
      async function f(x) {
        try {
          ++x;
          for (i = 0; i < 999; i = i + 1)
            console.log('value of x is:', await i);
      
        } catch (e) {
          console.log('error', e);
        }
      };
      
      f(x);
      console.log('Out');
      

      【讨论】:

      • Outvalue of x is 之前被记录
      • 是的,这基本上就是他的要求,即:“期望:'Out of the function' 会立即打印出来。'value of x...' 需要一些时间。”
      【解决方案5】:

      async 关键字使函数返回一个promise 或'thenable'。你需要等待你的承诺增量。

      建议了顶级等待,但在我们拥有它之前,您需要在另一个异步函数中调用或使用它。

      https://github.com/tc39/proposal-top-level-await

      var x = 0;
      
      async function increment(x) {
        ++x;
        for (var i = 0; i < 999999999; i = i + 1);
        console.log('value of x is: ' + x);
      };
      
      async function waitThenLog() {
        await increment(x);
        console.log('Out of the function');
      }
      
      waitThenLog();
      
      
      // or...
      
      increment(x).then(() => console.log('Out of the function'));
      

      【讨论】:

      • 具有讽刺意味的是,为了达到预期的效果,您还可以在“异步函数增量”上省略异步,因为增量调用将被阻塞。
      【解决方案6】:

      这是因为异步函数并不意味着它会一直结束持续下去。

      将异步 console.log 包装在 setTimeout 中,您将首先看到最后一个 console.log 打印。

      var x = 0;
      
      async function increment(x) {
        ++x;
        setTimeout(() => {
          console.log('value of x is: ' + x);
        }, 1000);
      };
      
      increment(x);
      
      console.log('Out of the function');

      这是一个小提琴:https://jsfiddle.net/Ldjma3s8/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-05-19
        • 1970-01-01
        • 2018-12-28
        • 2021-02-03
        • 1970-01-01
        • 2021-04-30
        • 2021-10-16
        • 2023-01-23
        相关资源
        最近更新 更多