async/await
时,它会阻塞Node.js线程直到执行下一行代码吗?async/await
时,它会阻塞Node.js线程直到执行下一行代码吗?async/await
不会阻塞整个解释器。Node.js 仍然将所有 JavaScript 作为单线程运行,即使一些代码正在等待 async/await
,其他事件仍然可以运行它们的事件处理程序(因此 Node.js 不会被阻塞)。事件队列仍在为其他事件提供服务。事实上,将解决 Promise 的事件将允许 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()
处理程序,并使代码看起来具有同步性(尽管它实际上并非同步)。最终,它是一种缩写,可让您用更少的代码编写异步代码。但需要记住,任何可能拒绝的Promise都必须在其周围某个位置有try/catch语句以捕获和处理该拒绝。
逻辑上,当node.js执行函数遇到 await
关键字时,可以将其视为以下内容:
async
,这意味着它将始终返回一个Promise。await
关键字时,它暂停了进一步执行该函数,直到被等待的Promise解决。fn().then()
)。此时,.then()
处理程序尚未执行,因为Promise尚未解决。await
关键字的原始函数调用仍然被挂起,但现在可以处理其他事件了。await
后的行上继续执行。如果还有更多的await
语句,则函数执行将再次被暂停,直到该Promise解决。return
语句或到达函数体的末尾。如果有一个return xxx
语句,则对xxx
求值后,其结果成为此async
函数已经返回的Promise的解析值。该函数现在已经执行完毕,并且它之前返回的Promise已解析。.then()
处理程序。.then()
处理程序运行之后,此async
函数的工作最终完成。因此,虽然整个解释器不会被阻止(仍然可以服务其他JavaScript事件),但包含 await
语句的特定async
函数的执行已被暂停,直到被等待的Promise解决。重要的是要理解上述第5步。当遇到第一个await
时,函数立即返回一个未解析的Promise,并且在解析Promise之前会执行该函数之后的代码。正是出于这个原因,整个解释器没有被阻止。执行继续进行,只有一个函数的内部被挂起,直到Promise得到解决。
async/await
提供了一种替代传统使用then
在promise上调用的方法。无论是Promises还是async
和await
都不会创建新线程。
当执行await
时,其后的表达式将被同步评估。该表达式应该评估为一个promise,但如果不是,则会包装为一个promise,就像你使用await Promise.resolve(expression)
一样。
一旦该表达式被评估,async
函数将返回--它返回一个promise。然后代码继续执行紧随该函数调用之后的任何代码(同一线程),直到调用堆栈为空。
在某个时间点,为了await
而评估的promise将会解析。这将在微任务队列中放置一个微任务。当JavaScript引擎在当前任务没有更多事情可做时,它将消耗微任务队列中的下一个事件(先进先出)。由于此微任务涉及已解决的promise,它将恢复async
函数的先前执行状态,并继续处理await
之后的任何内容。
函数可能会执行其他await
语句,并具有类似的行为,尽管函数现在不再返回到最初调用它的位置(因为该调用已经通过第一个await
处理),它仅仅返回,使调用堆栈为空,并留下JavaScript引擎处理微任务和任务队列。
所有这些都在同一线程中发生。
不,使用阻塞API调用在节点服务器中是不可以的
,async/await和同步版本有什么区别? - Toolkitasync/await
的行为,它不是阻塞的,因为代码会继续执行。 - trincotasync
函数,其中包含无限循环,在其中尝试使用超时记录某些内容 setTimeout( ()=>{ console.log(..) } )
- 你永远不会看到这个日志。 - fiderstring.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);
这个第二个例子使用了promises,但它仍然会阻塞主node线程,无法为其他请求/客户端提供服务。
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);
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
请求,则这4个示例中的每一个都将阻塞主线程,因为代码 string.match(/^(a|a)+$/)
是同步的,并且需要很长时间来处理。我已经更新了我的答案以澄清这一点。 - mikep异步函数使我们能够像编写同步代码一样编写基于 Promise 的代码,但不会阻塞执行线程。它通过事件循环异步操作。异步函数将始终返回一个值。仅使用 async 意味着将返回一个 Promise,如果没有返回 Promise,则 JavaScript 会自动将其包装在已解决的 Promise 中并带有其值。
在 Medium 上找到文章。 如何在 JavaScript 中使用 Async Await。
我刚刚有了一个“恍然大悟”的时刻,想要把它分享出来。"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()
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()
Promise.all()
非常有用,这个例子只是展示了一种天真的使用方法。请查看promise_variations.js以获取各种使用promise的方式。 - Chinoto Vokro
await
不是会'阻塞当前线程'直到等待的操作完成,但允许其他线程继续执行吗? - Pac0exec
或fork
命令时会形成一个新的进程而不是线程。 - Nidhin Davidasync/await
呢?我们可以只编写阻塞代码。 - Felix Kling