Breaking the async boundary

James Allardice

The event loop

  • Used by JS engines to schedule tasks
  • Most callbacks (e.g. setTimeout) are tasks
  • Tasks are scheduled to run sequentially
  • Different tasks have different stack frames

The problem

startTimer();
a();
setTimeout(b, 0);
c();
stopTimer();

How long does it take the following program, including `b`, to execute?

startTimer();
a();
setTimeout(b, 0); // asynchronous 
c();
stopTimer();
startTimer();                                // 1
a();
setTimeout(b, 0); // asynchronous 
c();
stopTimer();
startTimer();                                // 1
a();                                         // 2
setTimeout(b, 0); // asynchronous 
c();
stopTimer();
startTimer();                                // 1
a();                                         // 2
setTimeout(b, 0); // asynchronous 
c();                                         // 3
stopTimer();
startTimer();                                // 1
a();                                         // 2
setTimeout(b, 0); // asynchronous 
c();                                         // 3
stopTimer();                                 // 4
startTimer();                                // 1
a();                                         // 2
setTimeout(b, 0); // asynchronous            // 5
c();                                         // 3
stopTimer();                                 // 4

The problem

How can we associate data with the HTTP request in the following program?

// Assume `app` is an Express instance
app.use((req, res, next) => {
  
  // Generate a unique identifier for this request.
  const requestId = generateId();
  next();
});
// Assume `app` is an Express instance
app.use((req, res, next) => {
  
  // Generate a unique identifier for this request.
  const requestId = generateId();

  // Attach the identifier to the request object.
  req.id = requestId;

  next();
});

The problem

How can we capture a full stack trace when the following program dies?

Solutions

  • Domains
  • Async Listener
  • Continuation Local Storage (CLS)
  • AsyncWrap
  • Zones

Domains

  • Introduced in 0.8 but now deprecated (not yet removed)
  • "Handle multiple different IO operations as a single group"

Async Listener

  • Pull request to Node core never merged
  • Polyfill exists
  • Provides a mechanism to run functions just before and after a new task begins and ends

Continuation Local Storage

  • Built on Async Listener
  • Associate values with a namespace
  • Namespaces are groups of functions, synchronous or otherwise
  • Retrieve value from a namespace in any function in that group

AsyncWrap

  • Undocumented API
  • Available via process.binding('async_wrap')

Zones

  • Stage 0 ECMAScript proposal
  • Inspired by Dart
  • Numerous existing implementations (Angular zone.js, StrongLoop zone, can-zone)
  • All tasks scheduled within a zone will execute within that context
  • Provides hooks into the event loop

Zones

// Requires Node 0.12 or earlier.

require('zone').enable();

zone
  .create(function () {

    startTimer();

    a();
    setTimeout(b, 0);
    c();
  })
  .setCallback(function () {
    stopTimer();
  });

Using StrongLoop zone which is no longer maintained and requires Node.js 0.12 or earlier.

Thank you for listening!

Any questions?

Breaking the async boundary

By James Allardice

Breaking the async boundary

An exploration of the problems caused by ECMAScript's event loop and some of the possible solutions to those problems.

  • 1,183