为啥这个异步函数没有异步运行?

Posted

技术标签:

【中文标题】为啥这个异步函数没有异步运行?【英文标题】: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 部分。 【参考方案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 关键字做了两件事:

    它强制函数返回一个Promise 它允许你在函数体内使用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/

【讨论】:

以上是关于为啥这个异步函数没有异步运行?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个程序不异步运行?

为啥我的循环中的异步委托没有等待

继承人一些异步代码为亚马逊搜索提供硒队列,为啥我得到 AttributeError? __init__ 没有运行?

为啥我不能将异步代码作为同步运行 [重复]

有时,我的 AWS lambda 函数没有使用调用异步运行

如何使用调度库运行异步函数?