1. 原文

Faster async functions and promises


2. 摘要翻译

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.

JavaScript的异步处理不够快已经是名声在外了。更糟糕的是在JavaScript中对应用程序进行debug - 特别是Node.js服务器 - 很困难,特别是异步编程的情况。幸运的是,事情正在转变。这篇文章会探索我们在V8中是如何优化异步函数和promise的,并描述我们是如何提升异步代码的debugging体验的。

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) => {
        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(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);
  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 ReadableStreams. Here’s an example:


const http = require('http');

http.createServer((req, res) => {
  let body = '';
  req.on('data', (chunk) => {
    body += chunk;
  req.on('end', () => {

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:

幸运的是,ES2018中引入了一个很cool的功能,称为 async iteration,能简化这段代码:

const http = require('http');

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

Instead of putting the logic that deals with the actual request processing into two different callbacks — the 'data' and the 'end' callback — we can now put everything into a single async function instead, and use the new for await…of loop to iterate over the chunks asynchronously. We also added a try-catch block to avoid the unhandledRejection problem.

不再将请求处理的逻辑放在两个不同的回调函数里 - 'data' 以及 'end' 回调 - 我们现在可以将所有逻辑放在一个async函数里处理即可,并且使用新的for await…of循环来异步迭代chunks数据。我们还添加了一个try-catch代码块来防止unhandledRejection问题。

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函数从Node.js 8(V8 v6.2 / Chrome 62)就得到全面支持了,而async迭代以及generators则从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.

我们已经在V8 v5.5(Chrome 55 & Node.js 7)以及V8 v6.8(Chrome 68 & Node.js 10)版本之间显著提升了异步代码的性能。现在的性能已经可以让开发者放心安全使用这些新的语法功能,而不用担心性能问题。

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.

上图显示了doxbee benchmark,这个测试是用来衡量promise重度使用代码的性能。请注意,图表可视化了执行时长,这意味着越低越好。

The results on the parallel benchmark, which specifically stresses the performance of Promise.all(), are even more exciting:

下图显示了parallel benchmark,这个测试是用来压测Promise.all()的性能,很有趣:

We’ve managed to improve Promise.all performance by a factor of .


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

上图显示了一些重度使用promise和async函数的流行HTTP中间件框架的性能表现。请注意这幅图表显示了 requests / second 的数量,所以不像之前的图表,现在是越高越好。这些框架的性能在Node.js 7(V8 v5.5)和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 await to skip microticks 🐛

  • TurboFan,最新的优化编译器
  • Orinoco,新的GC垃圾回收器
  • 一个导致await跳过microticks的Node.js 8 bug

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

当我们在Node.js 8上线TurboFan的时候,得到了一个超级巨大的全面性能提升。

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 await 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:

放在最后说,但不代表最不重要,在Node.js 8中有一个bug,导致了在某些情况下await会跳过microticks,导致了更好的性能表现。这个bug来源于一个非自觉的spec违反,但它也给了我们一些优化的灵感。让我们从解释这个bug行为开始:

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 p, and awaits its result, but also chains two handlers onto it. In which order would you expect the console.log calls to execute?


Since p is fulfilled, you might expect it to print 'after:await' first and then the 'tick's. In fact, that’s the behavior you’d get in Node.js 8:

因为 p 已经被满足了,你可能觉得应该会先打印'after:await',然后才是'tick'。事实上,在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.

虽然这个行为结果符合直觉,但其实按照spec来说,它是不正确的。Node.js 10实现了正确的行为,先执行被链式附加的两个处理函数,然后才会继续这个async函数。

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/await and promises, and execute at the end of each task. The microtask queue is always emptied before execution returns to the event loop.


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.

如果想要了解更多细节,请查看Jake Archibald的解释帖 tasks, microtasks, queues, and schedules in the browser。在Node.js中的Task模型也是类似的。

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

// prints 42 on the next turn

You only get to the value of this promise p the next time microtasks are run. In other words, the above program is semantically equivalent to using Promise.resolve with the value:


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

The real power of async functions comes from await expressions, which cause the function execution to pause until a promise is resolved, and resume after fulfillment. The value of await 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 fetchStatus gets suspended on the await, and is later resumed when the fetch promise fulfills. This is more or less equivalent to chaining a handler onto the promise returned from fetch.


function fetchStatus(url) {
  return fetch(url).then(response => response.status);

That handler contains the code following the await in the async function.


Normally you’d pass a Promise to await, but you can actually wait on any arbitrary JavaScript value. If the value of the expression following the await is not a promise, it’s converted to a promise. That means you can await 42 if you feel like doing that:

一般来说你需要将Promise提供给await,但实际上你可以在任何JavaScript值上进行等待。如果await所接的表达式不是一个promise,它就会被转换成一个promise。这意味着你可以编写类似于await 42这样的代码,只要你想:

async function foo() {
  const v = await 42;
  return v;

const p = foo();
// → Promise

// prints `42` eventually

More interestingly, await works with any “thenable”, i.e. any object with a then 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:


class Sleep {
  constructor(timeout) {
    this.timeout = timeout;
  then(resolve, reject) {
    const startTime = Date.now();
    setTimeout(() => resolve(Date.now() - startTime),

(async () => {
  const actualTime = await new Sleep(1000);

Let’s see what V8 does for await under the hood, following the specification. Here’s a simple async function foo:


async function foo(v) {
  const w = await v;
  return w;

When called, it wraps the parameter v into a promise and suspends execution of the async function until that promise is resolved. Once that happens, execution of the function resumes and w gets assigned the value of the fulfilled promise. This value is then returned from the async function.


await under the hood

First of all, V8 marks this function as resumable, which means that execution can be suspended and later resumed (at await points). Then it creates the so-called implicit_promise, 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.

首先,V8将这个函数标记成可恢复,这意味着这个函数的执行能被暂停且能后续被恢复(在await代码点)。然后它会创建被称为隐式 promiseimplicit_promise),就是调用async函数时被返回的promise,并最终被resolve成async函数处理完成之后的值。

Then comes the interesting bit: the actual await. First the value passed to await 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 implicit_promise to the caller. Once the promise is fulfilled, execution of the async function is resumed with the value w from the promise, and the implicit_promise is resolved with w.

然后就是比较有趣的部分了:真正的await。首先被传递给await的值被包装成一个promise。然后,handlers被附加到这个被包装的promise上用以在promise被满足之后恢复函数的执行,之后async函数的执行就被暂停下来,将隐式 promise返回给调用者。一旦当promise被满足,async函数的执行就会被恢复,带有值wpromise被返回,然后隐式 promise将会被值wresolve。

In a nutshell, the initial steps for await v are:

概括来说,await v的初始步骤如下:

  1. Wrap v — the value passed to await — into a promise.
  2. Attach handlers for resuming the async function later.
  3. Suspend the async function and return the implicit_promise to the caller.
  • 将传给await的值v包装成一个promise
  • 将handlers附加到promise上,为了后续恢复async函数的执行
  • 暂停async函数,然后将隐式 promise返回给调用者

Let’s go through the individual operations step by step. Assume that the thing that is being awaited is already a promise, which was fulfilled with the value 42. Then the engine creates a new promise and resolves that with whatever’s being awaited. This does deferred chaining of these promises on the next turn, expressed via what the specification calls a PromiseResolveThenableJob.


Then the engine creates another so-called throwaway promise. It’s called throwaway because nothing is ever chained to it — it’s completely internal to the engine. This throwaway promise is then chained onto the promise, with appropriate handlers to resume the async function. This performPromiseThen operation is essentially what Promise.prototype.then() does, behind the scenes. Finally, execution of the async function is suspended, and control returns to the caller.

接下来引擎就创建了一个被称为throwaway的promise。它被称为throwaway是因为没有任何东西被链在它上面 - 它完全内置于引擎内。这个throwawaypromise接下来被链到之前的所说的附有handlers用来恢复async函数执行的promise之上。这个performPromiseThen操作,本质上就是Promise.prototype.then()在底层所做的事情。最终,async函数的执行被暂停,应用的控制被返回给调用者。