As long as the code contained inside the async/await is non blocking it won't block, for example db calls, network calls, filesystem calls.
But if the code contained inside async/await is blocking, then it will block the entire Node.js process, for example infinite loops, CPU intensive tasks like image processing, etc.
In essence async/await is a language level wrapper around Promises so that the code can have a synchronous 'look and feel'
async/await does not block the whole interpreter. node.js still runs all Javascript as single threaded and even though some code is waiting on an async/await, other events can still run their event handlers (so node.js is not blocked). The event queue is still being serviced for other events. In fact, it will be an event that resolves a promise that will allow the await to stop awaiting and run the following code.
Code like this:
await foo(); // foo is an async function that returns a promise
console.log("hello");
is analogous to this:
foo().then(() => {
console.log("hello");
});
So, await just puts the following code in that scope into an invisible .then() handler and everything else works pretty much the same as if it was actually written with a .then() handler.
So, await allows you to save the writing of the .then() handler and gives the code a synchronous look to it (though it isn't really synchronous). In the end it's a shorthand that lets you write async code with fewer lines of code. One does need to remember though that any promise that can reject must have a try/catch somewhere around it to catch and handle that rejection.
Logically, you can think of what node.js does when it encounters an await keyword when executing a function as the following:
Function call is made
The interpreter sees that the function is declared as async which means that it will always return a promise.
The interpreter starts executing the function.
When it encounters an await keyword, it suspends further execution of that function until the promise that is being awaited resolved.
The function then returns an unresolved promise.
At this point, the interpreter continues executing whatever comes after the function call (usually a fn().then() is followed by other lines of code). The .then() handlers are not executed yet because the promise is not yet resolved.
At some point this sequence of Javascript finishes and returns control back to the interpreter.
The interpreter is now free to serve other events from the event queue. The original function call that ran into an await keyword is still suspended, but other events can be processed now.
At some future point, the original promise that was being awaited gets resolved. When it's time for that to get processed in the event queue, the previously suspended function continues executing on the line after the await. If there are any more await statements, then the function execution is again suspended until that promise resolves.
Eventually the function hits a return statement or reaches the end of the function body. If there is a return xxx statement, then the xxx is evaluated and its result becomes the resolved value of the promise that this async function has already returned. The function is now done executing and the promise it previously returned has been resolved.
This will cause any .then() handlers attached to the promise that this function previously returned to get called.
After those .then() handlers run, the job of this async function is finally done.
So, while the whole interpreter doesn't block (other Javascript events can still be serviced), the execution of the specific async function that contains the await statement was suspended until the promise that was being awaited resolved. What's important to understand is step 5 above. When the first await is hit, the function immediately returns an unresolved promise and code after this function is executed (before the promise being awaited is resolved). It's for this reason that the whole interpreter is not blocked. Execution continues. Only the insides of one function are suspended until a promise is resolved.
async/await provide an alternative way for what you would traditionally do with then calls on a promise. Nor Promises, nor async nor await create new threads.
When await is executed, the expression that follows it is evaluated synchronously. That expression should evaluate to a promise, but if it is not, it is wrapped into one, as if you had await Promise.resolve(expression).
Once that expression is evaluated, the async function returns -- it returns a promise. Then code execution continues with whatever code follows that function call (same thread) until the call stack is empty.
At some point the promise -- that was evaluated for the await -- will resolve. This will put a microtask in a microtask queue. When the JavaScript engine has nothing more to do in the current task, it will consume the next event in the microtask queue (FIFO). As this microtask involves a resolved promise, it will restore the previous execution state of the async function and continue with whatever comes next after the await.
The function may execute other await statements, with similar behaviour, although the function now no longer returns to where it was originally called from (as that call was already processed with the first await), it merely returns leaving the call stack empty, and leaves the JavaScript engine to process the microtask and task queues.
I just had an "aha!" moment and thought that I'd pass it on. "await" does not return control directly to JavaScript - it returns control to the caller. Let me illustrate. Here is a program using callbacks:
The behavior that we want is
1) both steps are immediately started, and
2) when a step is ready to be handled (imagine an Ajax request, but here we just wait for some period of time), the handling of each step happens immediately.
The "handling" code here is console.log("step X handled"). That code (which in a real application can be quite long and possibly include nested awaits), is in a callback, but we'd prefer for it to be top level code in a function.
Here is equivalent code using async/await. Note that we had to create a sleep() function since we need to await on a function that returns a 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()
The important takeaway for me was that the await in step1() returns control to the main body code so that step2() can be called to start that step, and the await in step2() also returns to the main body code so that "all steps started" can be printed. Some people advocate that you use "await Promise.all()" to start multiple steps, then after that, handle all steps using the results (which will appear in an array). However, when you do that, no step is handled until all steps resolve. That's not ideal, and seems to be totally unnecessary.
Will async/await block a thread node.js? As @Nidhin David said it depends on what code you have inside async function - db calls, network calls, filesystem calls are not blocking but blocking are for example long for/while cycles, JSON stringify/parse and evil/vulnerable regular expressions (google for ReDoS Attacks). Each of four examples below will block the main thread if /test request is called because of code string.match(/^(a|a)+$/) is synchronous and takes long time to process.
This first example will block main node thread as expected and no other requests/clients can be served.
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);
This second example uses promises but it still block main node thread and no other requests/clients can be served.
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);
This third example uses async+await but it is also blocking (async+await is the same as native 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);
Fourth example uses setTimeout() which causes slow request seems to be served immediately (browser quickly gets "Done") but it is also blocking and any other fast requests will wait until evilRegExp() ends.
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);
Async functions enable us to write promise based code as if it were synchronous, but without blocking the execution thread. It operates asynchronously via the event-loop. Async functions will always return a value. Using async simply implies that a promise will be returned, and if a promise is not returned, JavaScript automatically wraps it in a resolved promise with its value.