isync / await会阻塞线程node.js

时间:2017-09-01 16:15:11

标签: javascript node.js

当在node.js函数中使用raster时,是否会阻塞node.js线程,直到它执行下一行代码?

6 个答案:

答案 0 :(得分:65)

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

这样的代码:

await foo();            // foo is an async function that returns a promise
console.log("hello");

类似于:

foo().then(() => {
    console.log("hello");
});

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

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

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

  1. 进行函数调用
  2. 解释器看到该函数声明为async,这意味着它将始终返回一个promise。
  3. 解释器开始执行该功能。
  4. 当遇到await关键字时,它会暂停该函数的进一步执行,直到正在等待的承诺得到解决。
  5. 然后该函数返回一个未解决的承诺。
  6. 此时,解释器继续执行函数调用之后的任何内容(通常是fn().then()后跟其他代码行)。然后.then()处理程序尚未执行,因为承诺尚未解决。
  7. 在某些时候,这个Javascript序列完成并将控制权返回给解释器。
  8. 解释器现在可以自由地从事件队列中提供其他事件。遇到await关键字的原始函数调用仍处于暂停状态,但现在可以处理其他事件。
  9. 在未来的某个时刻,正在等待的原始承诺得到解决。当在事件队列中处理该事件时,先前挂起的函数继续在await之后的行上执行。如果还有await个语句,那么函数执行将再次暂停,直到该保证结算为止。
  10. 最终函数命中return语句或到达函数体的末尾。如果存在return xxx语句,则评估xxx,其结果将成为此async函数已返回的promise的已解析值。该功能现在已完成执行,之前返回的承诺已解决。
  11. 这会导致任何.then()处理程序附加到此函数先前返回的promise中。
  12. 运行这些.then()处理程序后,此async函数的作业终于完成了。
  13. 因此,虽然整个解释器没有阻塞(其他Javascript事件仍然可以被服务),但是包含async语句的特定await函数的执行被暂停,直到承诺是正在等待解决。重要的是要理解上面的步骤5。当第一个await被命中时,该函数在执行此函数后(在解析承诺为awaited之前)立即返回未解析的promise和代码。出于这个原因,整个翻译都没有被阻止。执行继续。只有一个函数的内部才会被暂停,直到承诺得到解决。

答案 1 :(得分:8)

async/await只是then调用promise的语法糖。诺言,也不是async也不是await创造新的线索。

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

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

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

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

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

答案 2 :(得分:3)

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

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

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

答案 3 :(得分:3)

异步/等待会阻塞线程node.js吗?正如@Nidhin David所说,这取决于您在异步函数中拥有什么代码-数据库调用,网络调用,文件系统调用不会阻塞,但阻塞例如持续很长的时间/同时周期,JSON字符串化/解析和邪恶/脆弱的正则表达式(google进行ReDoS攻击。


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

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

您可以向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);

第三个示例使用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);

第四个示例使用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);

答案 4 :(得分:3)

异步功能使我们能够像编写同步代码一样编写基于promise的代码,但不会阻塞执行线程。它通过事件循环异步运行。异步函数将始终返回值。使用异步仅意味着将返回一个承诺,如果未返回一个承诺,则JavaScript会自动将其包装在带有其值的已解决的承诺中。

找到有关Medium的文章。 How to use Async Await in JavaScript.

答案 5 :(得分:1)

我刚刚有一个"啊哈!"那个时刻,我想我会把它传递下去。 "等待"不直接将控制权返回给JavaScript - 它将控制权返回给调用者。让我来说明一下。这是一个使用回调的程序:

console.log("begin");
step1(() => console.log("step 1 handled"));
step2(() => console.log("step 2 handled"));
console.log("all steps started");

// ----------------------------------------------

function step1(func) {

console.log("starting step 1");
setTimeout(func, 10000);
} // step1()

// ----------------------------------------------

function step2(func) {

console.log("starting step 2");
setTimeout(func, 5000);
} // step2()

我们想要的行为是 1)两个步骤都立即启动,并且 2)当一个步骤准备好被处理时(想象一下Ajax请求,但这里我们只是等待一段时间),每个步骤的处理都会立即发生。

"处理"这里的代码是console.log("步骤X处理")。该代码(在实际应用程序中可能很长并且可能包含嵌套等待)是在回调中,但我们更喜欢它是函数中的顶级代码。

这是使用async / await的等效代码。请注意,我们必须创建一个sleep()函数,因为我们需要等待一个返回promise的函数:

let sleep = ms => new Promise((r, j)=>setTimeout(r, ms));

console.log("begin");
step1();
step2();
console.log("all steps started");

// ----------------------------------------------

async function step1() {

console.log("starting step 1");
await sleep(10000);
console.log("step 1 handled");
} // step1()

// ----------------------------------------------

async function step2() {

console.log("starting step 2");
await sleep(5000);
console.log("step 2 handled");
} // step2()

对我来说重要的一点是,step1()中的await将控制返回到主体代码,以便可以调用step2()来启动该步骤,并且step2()中的await也返回到主体代码所以"所有步骤开始"可以打印。有些人主张你使用"等待Promise.all()"要启动多个步骤,然后使用结果(将出现在数组中)处理所有步骤。但是,当您这样做时,在所有步骤解决之前不会处理任何步骤。这不太理想,似乎完全没必要。