async/await会阻塞线程node.js

raj*_*ota 41 javascript node.js async-await

async/await在node.js函数中使用时,它会阻塞node.js线程,直到它执行下一行代码吗?

jfr*_*d00 88

async/await不会阻止整个翻译.node.js仍然将所有Javascript作为单线程运行,即使某些代码正在等待async/await,其他事件仍然可以运行其事件处理程序(因此node.js不会被阻止).事件队列仍在为其他事件提供服务.实际上,它将是一个事件,它解决了一个允许await停止等待并运行以下代码的承诺.

像这样的代码:

await foo();            // foo is an async function that returns a promise
console.log("hello");
Run Code Online (Sandbox Code Playgroud)

类似于:

foo().then(() => {
    console.log("hello");
});
Run Code Online (Sandbox Code Playgroud)

因此,await只需将该范围中的以下代码放入一个不可见的.then()处理程序中,其他所有内容的工作方式与使用.then()处理程序实际编写的内容完全相同.

因此,await允许您保存.then()处理程序的写入并为代码提供同步外观(尽管它不是真正的同步).最后,它是一种速记,可让您使用较少的代码行编写异步代码.人们确实需要记住,任何可以拒绝的承诺必须在它周围的某个地方有一个try/catch来捕获和处理拒绝.

从逻辑上讲,您可以考虑await在执行函数时遇到关键字时node.js 执行的操作如下:

  1. 进行函数调用
  2. 解释器看到函数被声明为async,这意味着它将始终返回一个promise.
  3. 解释器开始执行该功能.
  4. 当遇到await关键字时,它会暂停该函数的进一步执行,直到正在等待的promise被解决.
  5. 然后该函数返回一个未解决的promise.
  6. 此时,解释器继续执行函数调用之后的任何内容(通常是fn().then()其他代码行).然后.then()处理程序尚未执行,因为承诺尚未解决.
  7. 在某些时候,这个Javascript序列完成并将控制权返回给解释器.
  8. 解释器现在可以自由地从事件队列中提供其他事件.遇到await关键字的原始函数调用仍处于暂停状态,但现在可以处理其他事件.
  9. 在未来的某个时刻,正在等待的原始承诺得到解决.当它在事件队列中被处理时,先前挂起的函数继续在该行之后执行await.如果还有更多await语句,那么函数执行将再次暂停,直到该promise得到解决.
  10. 最终函数命中一个return语句或到达函数体的末尾.如果存在return xxx语句,则xxx对其进行求值,其结果将成为此async函数已返回的promise的已解析值.该函数现在已完成执行,并且之前返回的承诺已得到解决.
  11. 这将导致任何.then()处理程序附加到此函数先前返回的承诺被调用.
  12. 在这些.then()处理程序运行之后,async最终完成此功能的工作.

因此,虽然整个解释器没有阻塞(其他Javascript事件仍然可以被服务),但是async包含该await语句的特定函数的执行被暂停,直到正在等待的promise被解决.重要的是要理解上面的步骤5.当第一个await命中时,该函数在执行此函数后(在解析promise之前awaited)立即返回未解析的promise和代码.出于这个原因,整个翻译都没有被阻止.执行继续.只有一个函数的内部才会被暂停,直到承诺得到解决.

  • 我发现 https://v8.dev/blog/fast-async 作为获取此答案中解释的实际过程的详细信息的一个很好的参考 (2认同)

tri*_*cot 12

async/await对于then承诺的召唤来说只是语法糖.也没有承诺,也async没有await创造新的线索.

await被执行时,它后面的表达被同步评估.它应该是一个承诺,但如果不是,它就会被包装成一个,就好像你有await Promise.resolve(expression).

一旦评估了该表达式,async函数就会返回 - 它返回一个promise.然后代码执行继续执行函数调用之后的任何代码(相同的线程),直到调用堆栈为空.

在某些时候,为await意志评估的承诺将得到解决.这将在微任务队列中放置一个微任务.当JavaScript引擎在当前任务中没有其他任何操作时,它将使用微任务队列中的下一个事件.由于这个微任务涉及一个已解决的承诺,它将恢复该async函数的先前执行状态,并继续执行之后的下一步await.

该函数可以执行await具有类似行为的其他语句,尽管该函数现在不再返回到最初调用它的位置(因为该调用已经使用第一个调用await),它只返回使调用堆栈为空,并保留JavaScript引擎处理微任务和任务队列.

所有这些都发生在同一个线程中.

  • 仅供参考,`await` 有一些超越语法糖的力量。例如,如果 `await` 在 `while()` 循环或 `for` 循环内,它将以一种方式挂起该循环,除非以不使用该类型的完全不同的方式重写代码,否则无法完成的循环。因此,虽然许多园艺品种的用途本质上是语法糖,但它的功能不止于此。 (3认同)
  • 真正等待某事发生而不允许代码在发生之前在其他地方继续执行的代码是同步的,并且***阻塞***。我已经解释了 `async/await` 的行为,它不会在代码继续执行时阻塞。 (2认同)

Nid*_*vid 8

只要包含在 async/await 中的代码是非阻塞的,它就不会阻塞,例如数据库调用、网络调用、文件系统调用。

但是如果包含在 async/await 中的代码是阻塞的,那么它将阻塞整个 Node.js 进程,例如无限循环、图像处理等 CPU 密集型任务等。

本质上,async/await 是 Promises 的语言级包装器,因此代码可以具有同步的“外观和感觉”

  • 我很长时间以来在这个主题上看到的最佳答案。关于 async/await 有很多误解,因为大多数人认为 async。意味着非阻塞,但只有当代码在备用线程中执行或使用这样做的系统调用时,这才是正确的。您自己编写的大多数代码(例如执行某些操作的 for/循环)确实会阻塞,即使在 async/await 函数中也是如此。 (2认同)

mik*_*kep 6

async/await 会阻塞线程 node.js 吗?正如@Nidhin David 所说,这取决于您在异步函数中的代码 - db 调用、网络调用、文件系统调用不会阻塞,但阻塞是例如 long for/while 循环、JSON stringify/parse 和 evil/vulnerable 正则表达式(谷歌用于 ReDoS 攻击)。如果/test请求被调用,以下四个示例中的每一个都会阻塞主线程,因为代码string.match(/^(a|a)+$/)是同步的并且需要很长时间来处理。


第一个示例将按预期阻塞主节点线程,并且无法为其他请求/客户端提供服务。

var http = require('http');

// This regexp takes to long (if your PC runs it fast, try to add some more "a" to the start of string).
// With each "a" added time to complete is always doubled.
// On my PC 27 times of "a" takes 2,5 seconds (when I enter 28 times "a" it takes 5 seconds).
// https://en.wikipedia.org/wiki/ReDoS
function evilRegExp() {
    var string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaab';
    string.match(/^(a|a)+$/);
}

// Request to http://localhost:8080/ wil be served quickly - without evilRegExp() but request to
// http://localhost:8080/test/ will be slow and will also block any other fast request to http://localhost:8080/
http.createServer(function (req, res) {
    console.log("request", req.url);

    if (req.url.indexOf('test') != -1) {
      console.log('runing evilRegExp()');
      evilRegExp();
    }

    res.write('Done');
    res.end();
}).listen(8080);
Run Code Online (Sandbox Code Playgroud)

您可以对http://localhost:8080/运行许多并行请求,而且速度会很快。然后只运行一个慢速请求http://localhost:8080/test/并且在慢速(阻塞)请求结束之前不会提供其他请求(即使是那些在http://localhost:8080/处快速的请求)。


第二个示例使用了 Promise,但它仍会阻塞主节点线程,并且无法为其他请求/客户端提供服务。

var http = require('http');

function evilRegExp() {
    return new Promise(resolve => {
        var string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaab';
        string.match(/^(a|a)+$/);
        resolve();
    });
}

http.createServer(function (req, res) {
      console.log("request", req.url);

    if (req.url.indexOf('test') != -1) {
      console.log('runing evilRegExp()');
      evilRegExp();
    }

    res.write('Done');
    res.end();

}).listen(8080);
Run Code Online (Sandbox Code Playgroud)

第三个示例使用 async+await 但它也是阻塞的(async+await 与原生 Promise 相同)。

var http = require('http');

async function evilRegExp() {
    var string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaab';
    string.match(/^(a|a)+$/);
    resolve();
}

http.createServer(function (req, res) {
      console.log("request", req.url);

    if (req.url.indexOf('test') != -1) {
      console.log('runing evilRegExp()');
      await evilRegExp();
    }

    res.write('Done');
    res.end();

}).listen(8080);
Run Code Online (Sandbox Code Playgroud)

第四个示例使用 setTimeout() 导致慢速请求似乎立即得到服务(浏览器很快“完成”),但它也被阻塞,任何其他快速请求将等到 evilRegExp() 结束。

var http = require('http');

function evilRegExp() {
    var string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaab';
    string.match(/^(a|a)+$/);
}

http.createServer(function (req, res) {
      console.log("request", req.url);

    if (req.url.indexOf('test') != -1) {
      console.log('runing evilRegExp()');
      setTimeout(function() { evilRegExp(); }, 0);
    }

    res.write('Done');
    res.end();

}).listen(8080);
Run Code Online (Sandbox Code Playgroud)