Cara menggunakan async vs sync nodejs

Asynchronous processing in JavaScript traditionally had a reputation for not being particularly fast. To make matters worse, debugging live JavaScript applications — in particular Node.js servers — is no easy task, especially when it comes to async programming. Luckily the times, they are a-changin’. This article explores how we optimized async functions and promises in V8 (and to some extent in other JavaScript engines as well), and describes how we improved the debugging experience for async code.

Note: If you prefer watching a presentation over reading articles, then enjoy the video below! If not, skip the video and read on.

A new approach to async programming

From callbacks to promises to async functions

Before promises were part of the JavaScript language, callback-based APIs were commonly used for asynchronous code, especially in Node.js. Here’s an example:

function handler(done) {
validateParams((error) => {
if (error) return done(error);
dbQuery((error, dbResults) => {
if (error) return done(error);
serviceCall(dbResults, (error, serviceResults) => {
console.log(result);
done(error, serviceResults);
});
});
});
}

The specific pattern of using deeply-nested callbacks in this manner is commonly referred to as “callback hell”, because it makes the code less readable and hard to maintain.

Luckily, now that promises are part of the JavaScript language, the same code could be written in a more elegant and maintainable manner:

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}

Even more recently, JavaScript gained support for async functions. The above asynchronous code can now be written in a way that looks very similar to synchronous code:

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}

With async functions, the code becomes more succinct, and the control and data flow are a lot easier to follow, despite the fact that the execution is still asynchronous. (Note that the JavaScript execution still happens in a single thread, meaning async functions don’t end up creating physical threads themselves.)

From event listener callbacks to async iteration

Another asynchronous paradigm that’s especially common in Node.js is that of . Here’s an example:

const http = require('http');

http.createServer((req, res) => {
let body = '';
req.setEncoding('utf8');
req.on('data', (chunk) => {
body += chunk;
});
req.on('end', () => {
res.write(body);
res.end();
});
}).listen(1337);

This code can be a little hard to follow: the incoming data is processed in chunks that are only accessible within callbacks, and the end-of-stream signaling happens inside a callback too. It’s easy to introduce bugs here when you don’t realize that the function terminates immediately and that the actual processing has to happen in the callbacks.

Fortunately, a cool new ES2018 feature called async iteration can simplify this code:

const http = require('http');

http.createServer(async (req, res) => {
try {
let body = '';
req.setEncoding('utf8');
for await (const chunk of req) {
body += chunk;
}
res.write(body);
res.end();
} catch {
res.statusCode = 500;
res.end();
}
}).listen(1337);

Instead of putting the logic that deals with the actual request processing into two different callbacks — the

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
8 and the
function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
9 callback — we can now put everything into a single async function instead, and use the new
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
0 loop to iterate over the chunks asynchronously. We also added a
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
1 block to avoid the
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
2 problem.

You can already use these new features in production today! Async functions are fully supported starting with Node.js 8 (V8 v6.2 / Chrome 62), and async iterators and generators are fully supported starting with Node.js 10 (V8 v6.8 / Chrome 68)!

Async performance improvements

We’ve managed to improve the performance of asynchronous code significantly between V8 v5.5 (Chrome 55 & Node.js 7) and V8 v6.8 (Chrome 68 & Node.js 10). We reached a level of performance where developers can safely use these new programming paradigms without having to worry about speed.

The above chart shows the doxbee benchmark, which measures performance of promise-heavy code. Note that the charts visualize execution time, meaning lower is better.

The results on the parallel benchmark, which specifically stresses the performance of

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
3, are even more exciting:

We’ve managed to improve

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
4 performance by a factor of 8×.

However, the above benchmarks are synthetic micro-benchmarks. The V8 team is more interested in how our optimizations affect real-world performance of actual user code.

The above chart visualizes the performance of some popular HTTP middleware frameworks that make heavy use of promises and

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
5 functions. Note that this graph shows the number of requests/second, so unlike the previous charts, higher is better. The performance of these frameworks improved significantly between Node.js 7 (V8 v5.5) and Node.js 10 (V8 v6.8).

These performance improvements are the result of three key achievements:

  • TurboFan, the new optimizing compiler 🎉
  • Orinoco, the new garbage collector 🚛
  • a Node.js 8 bug causing
    async function handler() {
    await validateParams();
    const dbResults = await dbQuery();
    const results = await serviceCall(dbResults);
    console.log(results);
    return results;
    }
    6 to skip microticks 🐛

When we launched TurboFan in Node.js 8, that gave a huge performance boost across the board.

We’ve also been working on a new garbage collector, called Orinoco, which moves garbage collection work off the main thread, and thus improves request processing significantly as well.

And last but not least, there was a handy bug in Node.js 8 that caused

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 to skip microticks in some cases, resulting in better performance. The bug started out as an unintended spec violation, but it later gave us the idea for an optimization. Let’s start by explaining the buggy behavior:

const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));

The above program creates a fulfilled promise

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
8, and
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6s its result, but also chains two handlers onto it. In which order would you expect the
const http = require('http');

http.createServer((req, res) => {
let body = '';
req.setEncoding('utf8');
req.on('data', (chunk) => {
body += chunk;
});
req.on('end', () => {
res.write(body);
res.end();
});
}).listen(1337);
0 calls to execute?

Since

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
8 is fulfilled, you might expect it to print
const http = require('http');

http.createServer((req, res) => {
let body = '';
req.setEncoding('utf8');
req.on('data', (chunk) => {
body += chunk;
});
req.on('end', () => {
res.write(body);
res.end();
});
}).listen(1337);
2 first and then the
const http = require('http');

http.createServer((req, res) => {
let body = '';
req.setEncoding('utf8');
req.on('data', (chunk) => {
body += chunk;
});
req.on('end', () => {
res.write(body);
res.end();
});
}).listen(1337);
3s. In fact, that’s the behavior you’d get in Node.js 8:

The
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 bug in Node.js 8

Although this behavior seems intuitive, it’s not correct according to the specification. Node.js 10 implements the correct behavior, which is to first execute the chained handlers, and only afterwards continue with the async function.

Node.js 10 no longer has the
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 bug

This “correct behavior” is arguably not immediately obvious, and was actually surprising to JavaScript developers, so it deserves some explanation. Before we dive into the magical world of promises and async functions, let’s start with some of the foundations.

Tasks vs. microtasks

On a high level there are tasks and microtasks in JavaScript. Tasks handle events like I/O and timers, and execute one at a time. Microtasks implement deferred execution for

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
5/
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 and promises, and execute at the end of each task. The microtask queue is always emptied before execution returns to the event loop.

The difference between microtasks and tasks

For more details, check out Jake Archibald’s explanation of tasks, microtasks, queues, and schedules in the browser. The task model in Node.js is very similar.

Async functions

According to MDN, an async function is a function which operates asynchronously using an implicit promise to return its result. Async functions are intended to make asynchronous code look like synchronous code, hiding some of the complexity of the asynchronous processing from the developer.

The simplest possible async function looks like this:

async function computeAnswer() {
return 42;
}

When called it returns a promise, and you can get to its value like with any other promise.

const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn

You only get to the value of this promise

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
8 the next time microtasks are run. In other words, the above program is semantically equivalent to using
const http = require('http');

http.createServer((req, res) => {
let body = '';
req.setEncoding('utf8');
req.on('data', (chunk) => {
body += chunk;
});
req.on('end', () => {
res.write(body);
res.end();
});
}).listen(1337);
9 with the value:

function computeAnswer() {
return Promise.resolve(42);
}

The real power of async functions comes from

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 expressions, which cause the function execution to pause until a promise is resolved, and resume after fulfillment. The value of
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 is that of the fulfilled promise. Here’s an example showing what that means:

async function fetchStatus(url) {
const response = await fetch(url);
return response.status;
}

The execution of

const http = require('http');

http.createServer(async (req, res) => {
try {
let body = '';
req.setEncoding('utf8');
for await (const chunk of req) {
body += chunk;
}
res.write(body);
res.end();
} catch {
res.statusCode = 500;
res.end();
}
}).listen(1337);
2 gets suspended on the
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6, and is later resumed when the
const http = require('http');

http.createServer(async (req, res) => {
try {
let body = '';
req.setEncoding('utf8');
for await (const chunk of req) {
body += chunk;
}
res.write(body);
res.end();
} catch {
res.statusCode = 500;
res.end();
}
}).listen(1337);
4 promise fulfills. This is more or less equivalent to chaining a handler onto the promise returned from
const http = require('http');

http.createServer(async (req, res) => {
try {
let body = '';
req.setEncoding('utf8');
for await (const chunk of req) {
body += chunk;
}
res.write(body);
res.end();
} catch {
res.statusCode = 500;
res.end();
}
}).listen(1337);
4.

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
0

That handler contains the code following the

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 in the async function.

Normally you’d pass a

const http = require('http');

http.createServer(async (req, res) => {
try {
let body = '';
req.setEncoding('utf8');
for await (const chunk of req) {
body += chunk;
}
res.write(body);
res.end();
} catch {
res.statusCode = 500;
res.end();
}
}).listen(1337);
7 to
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6, but you can actually wait on any arbitrary JavaScript value. If the value of the expression following the
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 is not a promise, it’s converted to a promise. That means you can
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
0 if you feel like doing that:

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
1

More interestingly,

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 works with any “thenable”, i.e. any object with a
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
2 method, even if it’s not a real promise. So you can implement funny things like an asynchronous sleep that measures the actual time spent sleeping:

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
2

Let’s see what V8 does for

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 under the hood, following the . Here’s a simple async function
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
4:

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
3

When called, it wraps the parameter

const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
5 into a promise and suspends execution of the async function until that promise is resolved. Once that happens, execution of the function resumes and
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
6 gets assigned the value of the fulfilled promise. This value is then returned from the async function.

async function handler() { await validateParams(); const dbResults = await dbQuery(); const results = await serviceCall(dbResults); console.log(results); return results;}6 under the hood

First of all, V8 marks this function as resumable, which means that execution can be suspended and later resumed (at

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 points). Then it creates the so-called
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
9, which is the promise that is returned when you invoke the async function, and that eventually resolves to the value produced by the async function.

Comparison between a simple async function and what the engine turns it into

Then comes the interesting bit: the actual

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6. First the value passed to
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 is wrapped into a promise. Then, handlers are attached to this wrapped promise to resume the function once the promise is fulfilled, and execution of the async function is suspended, returning the
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
9 to the caller. Once the
async function computeAnswer() {
return 42;
}
3 is fulfilled, execution of the async function is resumed with the value
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
6 from the
async function computeAnswer() {
return 42;
}
3, and the
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
9 is resolved with
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
6.

In a nutshell, the initial steps for

async function computeAnswer() {
return 42;
}
8 are:

  1. Wrap
    const p = Promise.resolve();

    (async () => {
    await p; console.log('after:await');
    })();

    p.then(() => console.log('tick:a'))
    .then(() => console.log('tick:b'));
    5 — the value passed to
    async function handler() {
    await validateParams();
    const dbResults = await dbQuery();
    const results = await serviceCall(dbResults);
    console.log(results);
    return results;
    }
    6 — into a promise.
  2. Attach handlers for resuming the async function later.
  3. Suspend the async function and return the
    const p = Promise.resolve();

    (async () => {
    await p; console.log('after:await');
    })();

    p.then(() => console.log('tick:a'))
    .then(() => console.log('tick:b'));
    9 to the caller.

Let’s go through the individual operations step by step. Assume that the thing that is being

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6ed is already a promise, which was fulfilled with the value
const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
3. Then the engine creates a new
async function computeAnswer() {
return 42;
}
3 and resolves that with whatever’s being
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6ed. This does deferred chaining of these promises on the next turn, expressed via what the specification calls a .

Then the engine creates another so-called

const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
7 promise. It’s called throwaway because nothing is ever chained to it — it’s completely internal to the engine. This
const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
7 promise is then chained onto the
async function computeAnswer() {
return 42;
}
3, with appropriate handlers to resume the async function. This
function computeAnswer() {
return Promise.resolve(42);
}
0 operation is essentially what
function computeAnswer() {
return Promise.resolve(42);
}
1 does, behind the scenes. Finally, execution of the async function is suspended, and control returns to the caller.

Execution continues in the caller, and eventually the call stack becomes empty. Then the JavaScript engine starts running the microtasks: it runs the previously scheduled , which schedules a new to chain the

async function computeAnswer() {
return 42;
}
3 onto the value passed to
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6. Then, the engine returns to processing the microtask queue, since the microtask queue must be emptied before continuing with the main event loop.

Next up is the , which fulfills the

async function computeAnswer() {
return 42;
}
3 with the value from the promise we’re
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6ing —
const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
3 in this case — and schedules the reaction onto the
const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
7 promise. The engine then returns to the microtask loop again, which contains a final microtask to be processed.

Now this second propagates the resolution to the

const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
7 promise, and resumes the suspended execution of the async function, returning the value
const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
3 from the
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6.

Summary of the overhead of
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6

Summarizing what we’ve learned, for each

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 the engine has to create two additional promises (even if the right hand side is already a promise) and it needs at least three microtask queue ticks. Who knew that a single
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 expression resulted in that much overhead?!

Let’s have a look at where this overhead comes from. The first line is responsible for creating the wrapper promise. The second line immediately resolves that wrapper promise with the

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6ed value
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
5. These two lines are responsible for one additional promise plus two out of the three microticks. That’s quite expensive if
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
5 is already a promise (which is the common case, since applications normally
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 on promises). In the unlikely case that a developer
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6s on e.g.
const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
3, the engine still needs to wrap it into a promise.

As it turns out, there’s already a operation in the specification that only performs the wrapping when needed:

This operation returns promises unchanged, and only wraps other values into promises as necessary. This way you save one of the additional promises, plus two ticks on the microtask queue, for the common case that the value passed to

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 is already a promise. This new behavior is already . For V8 v7.1, the new behavior can be enabled using the
function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
06 flag. We’ve proposed this change to the ECMAScript specification as well.

Here’s how the new and improved

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 works behind the scenes, step by step:

Let’s assume again that we

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 a promise that was fulfilled with
const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
3. Thanks to the magic of the
async function computeAnswer() {
return 42;
}
3 now just refers to the same promise
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
5, so there’s nothing to do in this step. Afterwards the engine continues exactly like before, creating the
const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
7 promise, scheduling a to resume the async function on the next tick on the microtask queue, suspending execution of the function, and returning to the caller.

Then eventually when all JavaScript execution finishes, the engine starts running the microtasks, so it executes the . This job propagates the resolution of

async function computeAnswer() {
return 42;
}
3 to
const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
7, and resumes the execution of the async function, yielding
const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
3 from the
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6.

Summary of the reduction in
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 overhead

This optimization avoids the need to create a wrapper promise if the value passed to

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 is already a promise, and in that case we go from a minimum of three microticks to just one microtick. This behavior is similar to what Node.js 8 does, except that now it’s no longer a bug — it’s now an optimization that is being standardized!

It still feels wrong that the engine has to create this

const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
7 promise, despite being completely internal to the engine. As it turns out, the
const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
7 promise was only there to satisfy the API constraints of the internal
function computeAnswer() {
return Promise.resolve(42);
}
0 operation in the spec.

This was recently addressed in an editorial change to the ECMAScript specification. Engines no longer need to create the

const p = computeAnswer();
// → Promise

p.then(console.log);
// prints 42 on the next turn
7 promise for
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 — most of the time.

Comparison of
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 code before and after the optimizations

Comparing

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 in Node.js 10 to the optimized
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 that’s likely going to be in Node.js 12 shows the performance impact of this change:

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
5/
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 outperforms hand-written promise code now. The key takeaway here is that we significantly reduced the overhead of async functions — not just in V8, but across all JavaScript engines, by patching the spec.

Update: As of V8 v7.2 and Chrome 72,

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
06 is enabled by default. The patch to the ECMAScript specification was merged.

Improved developer experience

In addition to performance, JavaScript developers also care about the ability to diagnose and fix problems, which is not always easy when dealing with asynchronous code. Chrome DevTools supports async stack traces, i.e. stack traces that not only include the current synchronous part of the stack, but also the asynchronous part:

Cara menggunakan async vs sync nodejs

This is an incredibly useful feature during local development. However, this approach doesn’t really help you once the application is deployed. During post-mortem debugging, you’ll only see the

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
33 output in your log files, and that doesn’t tell you anything about the asynchronous parts.

We’ve recently been working on zero-cost async stack traces which enrich the

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
33 property with async function calls. “Zero-cost” sounds exciting, doesn’t it? How can it be zero-cost, when the Chrome DevTools feature comes with major overhead? Consider this example where
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
4 calls
function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
36 asynchronously, and
function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
36 throws an exception after
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6ing a promise:

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
4

Running this code in Node.js 8 or Node.js 10 results in the following output:

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
5

Note that although the call to

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
39 causes the error,
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
4 is not part of the stack trace at all. This makes it tricky for JavaScript developers to perform post-mortem debugging, independent of whether your code is deployed in a web application or inside of some cloud container.

The interesting bit here is that the engine knows where it has to continue when

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
36 is done: right after the
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 in function
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
4. Coincidentally, that’s also the place where the function
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
4 was suspended. The engine can use this information to reconstruct parts of the asynchronous stack trace, namely the
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 sites. With this change, the output becomes:

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
6

In the stack trace, the topmost function comes first, followed by the rest of the synchronous stack trace, followed by the asynchronous call to

function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
36 in function
const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
4. This change is implemented in V8 behind the new
function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
48 flag. Update: As of V8 v7.3,
function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
48 is enabled by default.

However, if you compare this to the async stack trace in Chrome DevTools above, you’ll notice that the actual call site to

const p = Promise.resolve();

(async () => {
await p; console.log('after:await');
})();

p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'));
4 is missing from the asynchronous part of the stack trace. As mentioned before, this approach utilizes the fact that for
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 the resume and suspend locations are the same — but for regular
function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
52 or
function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
53 calls, this is not the case. For more background, see Mathias Bynens’s explanation on why
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 beats
function handler() {
return validateParams()
.then(dbQuery)
.then(serviceCall)
.then(result => {
console.log(result);
return result;
});
}
52.

Conclusion

We made async functions faster thanks to two significant optimizations:

  • the removal of two extra microticks, and
  • the removal of the
    const p = computeAnswer();
    // → Promise

    p.then(console.log);
    // prints 42 on the next turn
    7 promise.

On top of that, we’ve improved the developer experience via zero-cost async stack traces, which work with

async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
6 in async functions and
async function handler() {
await validateParams();
const dbResults = await dbQuery();
const results = await serviceCall(dbResults);
console.log(results);
return results;
}
3.

And we also have some nice performance advice for JavaScript developers:

  • favor
    async function handler() {
    await validateParams();
    const dbResults = await dbQuery();
    const results = await serviceCall(dbResults);
    console.log(results);
    return results;
    }
    5 functions and
    async function handler() {
    await validateParams();
    const dbResults = await dbQuery();
    const results = await serviceCall(dbResults);
    console.log(results);
    return results;
    }
    6 over hand-written promise code, and
  • stick to the native promise implementation offered by the JavaScript engine to benefit from the shortcuts, i.e. avoiding two microticks for
    async function handler() {
    await validateParams();
    const dbResults = await dbQuery();
    const results = await serviceCall(dbResults);
    console.log(results);
    return results;
    }
    6.

  1. Thanks to Matteo Collina for pointing us to .

  2. V8 still needs to create the

    const p = computeAnswer();
    // → Promise

    p.then(console.log);
    // prints 42 on the next turn
    7 promise if
    function handler() {
    return validateParams()
    .then(dbQuery)
    .then(serviceCall)
    .then(result => {
    console.log(result);
    return result;
    });
    }
    63 are being used in Node.js, since the
    function handler() {
    return validateParams()
    .then(dbQuery)
    .then(serviceCall)
    .then(result => {
    console.log(result);
    return result;
    });
    }
    64 and
    function handler() {
    return validateParams()
    .then(dbQuery)
    .then(serviceCall)
    .then(result => {
    console.log(result);
    return result;
    });
    }
    65 hooks are run within the context of the
    const p = computeAnswer();
    // → Promise

    p.then(console.log);
    // prints 42 on the next turn
    7 promise.

Cara menggunakan async vs sync nodejs

Cara menggunakan async vs sync nodejs

Posted by Maya Armyanova (@Zmayski), always-awaiting anticipator, and Benedikt Meurer (@bmeurer), professional performance promiser.

Apakah JavaScript async atau sync?

Jawabannya adalah JavaScript melakukannya secara asynchronous. Pada konsep asynchronous, code akan dieksekusi tanpa menunggu eksekusi code lain selesai sehingga seakan-akan dieksekusi secara bersamaan.

Kenapa menggunakan async Await?

Async-await bisa dikatakan sebagai cara mudah menggunakan JavaScript Promise yang agak sulit dipahami. JavaScript bersifat non blocking - tidak menunggu suatu fungsi selesai untuk melanjutkan ke fungsi yang lain. Kelebihannya - efisien dan cepat, karena tidak menunggu.

Apa perbedaan dari asynchronous dan synchronous?

Synchronous adalah pembelajaran yang berpedoman pada jadwal atau kerangka waktu pelajaran. Peserta didik dapat mengakses materi maupun tugas dalam kurun waktu tertentu. Sedangkan asynchronous memiliki waktu yang lebih fleksibel.

Apakah Node JS asynchronous?

Asynchronous & Event-driven Semua API dari Node.js bersifat asynchronous, artinya tidak memblokir proses lain sembari menunggu satu proses selesai. Server Node.js akan melanjutkan ke ke pemanggilan API berikutnya lalu memanfaatkan mekanisme event notification untuk mendapatkan respon dari panggilan API sebelumnya.