为什么这个异步函数不是异步运行的?

Why is this async function not behaving asynchronously?

我正在学习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' 立即打印。 'value of x...' 需要一些时间。
现实: 'value of x...' 在延迟后首先打印。然后打印 'Out of the function'。

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

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

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

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

来自MDN

The body of an async function can be thought of as being split by zero or more await expressions. Top-level code, up to and including the first await expression (if there is one), is run synchronously. In this way, an async function without an await expression will run synchronously

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

 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');

那是因为函数是异步的并不意味着它总是 end 最后。

将异步 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');

这是一个Fiddle:https://jsfiddle.net/Ldjma3s8/

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

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

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');

async 关键字使函数 return 成为承诺或 'thenable'。您需要等待您的承诺增量。

顶级 await 已被提议,但在我们拥有它之前您需要在另一个异步函数中进行调用或使用 then。

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'));

TL;DR:您必须 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运行时是一个基于event-loop的单线程运行时,这意味着没有concurrency/paralell代码执行(除非你使用Workerchild_process,那是另一个话题了)。

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

通过这些 API 作业被调度,缓冲在 微任务队列 中,空闲时将被事件循环调用和执行。因此,异步性实际上只是调度。


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

async function 关键字做两件事:

  1. 它强制函数 return a Promise
  2. 它允许您在函数体内使用 await 关键字

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

因此,如果您不通过使用 await 关键字来涉及 event-loop 调度程序,那么执行顺序将保持正常。从而解释你的情况。


如果你理解 async 函数只是 Promise 的语法糖,下一个常见的误解是 new Promise(callback).

的执行顺序

案例 1:

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

console.log('Alice comes first!')

:谁先来?
A:鲍勃先来。

案例 2:

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


console.log('Alice comes first!')

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

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

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