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?

Made with Slides.com