当async/await
在一个的node.js函数使用时,它会阻塞的node.js线程,直到它执行代码的下一行?
await
'阻塞当前线程',而是允许其他线程继续执行?
exec
或时fork
,将形成一个新进程,而不是一个新线程。
async/await
?我们可以只写阻塞代码。
当async/await
在一个的node.js函数使用时,它会阻塞的node.js线程,直到它执行代码的下一行?
await
'阻塞当前线程',而是允许其他线程继续执行?
exec
或时fork
,将形成一个新进程,而不是一个新线程。
async/await
?我们可以只写阻塞代码。
Answers:
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()
处理程序的编写并为代码提供同步的外观(尽管它并不是真正的同步)。最后,它是让您用更少的代码行编写异步代码的捷径。确实需要记住的是,任何可以拒绝的承诺都必须在其周围进行尝试/捕获才能捕获并处理该拒绝。
从逻辑上讲,您可以考虑await
执行以下函数时,node.js在遇到关键字时会执行以下操作:
async
,这意味着它将始终返回promise。await
关键字时,它将中止该功能的进一步执行,直到等待解决的承诺为止。fn().then()
之后是其他代码行)。的.then()
,因为承诺尚未解决处理程序尚未执行。await
关键字的原始函数调用仍被暂停,但其他事件现在可以处理。await
。如果还有其他await
语句,则函数执行将再次暂停,直到该承诺解决为止。return
语句或到达函数主体的末尾。如果有一条return xxx
语句,则对xxx
进行评估,其结果成为该async
函数已经返回的Promise的已解析值。该函数现在已完成执行,并且先前返回的promise已解决。.then()
附加到该函数先前返回的promise的任何处理程序被调用。.then()
处理程序运行后,此async
功能的工作终于完成。因此,尽管整个解释器没有阻塞(仍然可以处理其他Javascript事件),但是async
包含该await
语句的特定函数的执行被暂停,直到等待解决的诺言为止。要理解的重要步骤是上面的步骤5。当第一个await
被点击时,该函数会立即返回一个未解决的promise和执行此函数后的代码(在promise被awaited
解决之前)。出于这个原因,整个解释器不会被阻塞。执行继续。只有一个功能的内部才被挂起,直到承诺被解决。
async/await
提供传统then
上对诺言的传统处理方式。也没有承诺,也async
没有await
创建新线程。
当await
执行时,它后面的表达式将被同步求值。该表达式的计算结果应为一个诺言,但如果不是,则将其包装成一个诺言,就像您拥有一样await Promise.resolve(expression)
。
对该表达式求值后,该async
函数将返回-它返回一个promise。然后,代码继续执行该函数调用之后的任何代码(同一线程),直到调用堆栈为空。
在某个时候,经过评估的承诺await
将会解决。这会将微任务放入微任务队列。当JavaScript引擎在当前任务中无事可做时,它将消耗微任务队列(FIFO)中的下一个事件。由于此微任务涉及已解决的Promise,它将恢复该async
函数的先前执行状态,并继续执行之后的任何后续操作await
。
该函数可以执行其他await
具有类似行为的语句,尽管该函数现在不再返回到最初从其调用的位置(因为该调用已由第一个调用处理过await
),它仅返回而使调用栈为空,并保留了JavaScript引擎来处理微任务和任务队列。
所有这些都在同一线程中发生。
No, it is not OK to use a blocking API call in a node server
,异步/等待版本和同步版本之间有什么区别?
async/await
随着代码继续执行,该行为不会受到阻碍。
await
除了语法糖以外,还有其他功能。例如,如果await
在一个while()
循环或一个for
循环内,它将以某种方式暂停该循环,除非您以不使用该类型循环的完全不同的方式重写代码。因此,尽管许多花园品种的用途基本上都是语法糖,但它所具有的功能还不止这些。
只要async / await中包含的代码是非阻塞的,它就不会阻塞,例如db调用,网络调用,文件系统调用。
但是,如果async / await中包含的代码被阻塞,那么它将阻塞整个Node.js进程,例如无限循环,图像处理等CPU密集型任务等。
本质上,async / await是围绕Promises进行的语言级包装,因此代码可以具有同步的“外观”
async
内部无限循环的函数,while(1)
然后尝试记录具有超时的内容setTimeout( ()=>{ console.log(..) } )
-您将永远不会看到此日志
异步/等待会阻塞线程node.js吗?正如@Nidhin David所说,这取决于异步函数中包含的代码-数据库调用,网络调用,文件系统调用不会阻塞,但阻塞例如持续很长的时间/同时周期,JSON字符串化/解析和邪恶/脆弱的正则表达式(谷歌用于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);
您可以对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);
/test
因为代码string.match(/^(a|a)+$/)
是同步的并且处理时间很长,所以调用请求,这4个示例中的每个示例都会阻塞主线程。我已经更新了答案以澄清这一点。
我刚听到一个“啊哈!” 片刻,以为我会继续下去。“ await”不会直接将控制权返回给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已处理”)。该代码(在实际的应用程序中可能会很长,并且可能包含嵌套的等待),位于回调中,但我们希望它是函数中的顶级代码。
这是使用异步/等待的等效代码。请注意,我们必须创建一个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也返回主体代码,以使“所有步骤已开始”可以打印。有人主张您使用“ await Promise.all()”启动多个步骤,然后再使用结果(将出现在数组中)处理所有步骤。但是,这样做时,直到所有步骤都解决后,才会处理任何步骤。那不是理想的,似乎完全没有必要。
Promise.all()
综上所述,该示例非常有用,该示例仅显示了一种简单的用法。
异步功能使我们能够像编写同步代码一样编写基于promise的代码,但不会阻塞执行线程。它通过事件循环异步运行。异步函数将始终返回值。使用异步仅意味着将返回一个承诺,如果未返回一个承诺,JavaScript会自动将其包装在带有其值的已解决的承诺中。
在Medium上找到文章。 如何在JavaScript中使用Async Await。