The Continuation Moodel - A Model for Asynchronous Execution in JavaScript
This is an effort to formalize & visualize “asynchronous context” in Node.js applications.
The content here is a “simple summary” of more in-depth work, namely
- A DLS 2017 paper that formally defines semantics of async execution in javascript
- A “translation” of the above concepts, without the academic assumptions & formalisms. (WIP)
This page is a companion to the above. The intention is to easily bring an understanding of the asynchronous execution model formally defined above to a wide audience of Node.js and Javascript developers.
Why do we care?
Javascript is a single-threaded language, which simplifies many things. To prevent blocking IO, operations are pushed onto the background and associated with callback functions written in JavaScript. When IO operations complete, the callback is pushed onto a queue for execution by Node’s “event loop”. This is explained in more detail here.
While this model has many benefits, one of the key challenges is maintaing “context” when asynchronous callbacks are invoked. The papers above describe “asynchronous context” in a much more rigorous way, but for our purposes, we’ll think of “asynchronous context” as the ability to answer, at any given point in program execution, “what was the path of asynchronous functions that got me here”?
Terminology
One of the key challenges with “asynchronous context” is the lack of agreed upon terminology and semantics. Let’s define some:
-
Execution Frame- AnExecution Frameis a period of program execution, defined precisely as the period of time that a special function, called aContinuation, is executing. At a lower level of abstraction, you can think of anExecution Frameas the period of time from when specific call frame is pushed on the stack, until that call frame is unwound off of the stack. Not all functions areContinuations(more on that below). A specific instance of aContinuationcan be invoked multiple times; each invocation corresponds to a uniqueExecution Frame. -
Continuation- AContinuationis a JavaScript function created in oneExecution Frameand passed to a host library to be invoked later. Upon invocation, aContinuationcreates a new uniqueExecution Frame. For example, when we callsetTimeout(function c() {}, 1000),setTimeoutis invoked in oneExecution Frame, with the caller passing aContinution(namelyc) as a parameter. Whencis invoked after the timeout period, a newExecution Frameis created, and whenccompletes, thatExecution Frameis completed. -
Continuation Point- Functions that accept aContinuationas a parameter are calledContinuation Points.Continuation Pointsare determined by convention of the host. Some examples includesetTimeoutandPromise.then. Note that not all functions that take a function as a parameter areContinuation Points- the parameter must be invoked asynchronously. i.e., functions passed as parameters and invoked in the currentExecution Frameare notContinuation Points. For example,Array.prototyp.forEachis not considered aContinuation Point. -
Link Point- ALink Pointis point in program execution where aContinuation Pointis invoked. This creates a logical “binding” between the currentExecution Frameand theContinuationpassed as a parameter. We call this binding theLinking Context. -
Ready Point- AReady Pointis a point in program execution where a previously linkedContinuationis made “ready” to execute. This creates a logical “binding” between the Continuation and the currentExecution Frame. This binding is called theReady Context(sometimes called aCausal Context). Generally, theReady Pointalways occurs at or after theLink Point. Promises, however, are different. For promises, theReady Pointoccurs when the previous promise in the promise chain is resolved.
Events
The above definitions map nicely to a set of four events generated at runtime. These events let us track “async context”:
executeBegin- indicates the start of anExecution Frame.link- indicates aContinuation Pointwas called andContinuationwas “pooled” for later execution.ready- indicates aReady Pointwas reached.executeEnd- indicates the end of of anExecution Frame.
For example, consider the code below:
console.log('starting');
Promise p = new Promise((reject, resolve) => {
setTimeout(function f1() {
console.log('resolving promise');
resolve(true);
}, 100);
}).then(function f2() {
console.log('in then');
}
Given our model, this would produce the following event stream:
```json
{"event": "executeBegin", "executeID": 0 } // main program body is starting
// starting
{"event": "link", "executeID":0, "linkID": 1} // indicates f1() was "linked" in the call to "setTimeout()"
{"event": "ready", "executeID":0, "linkID": 1, "readyID": 2}
{"event": "link", "executeID":0, "linkID": 3} // indicates f2() was "linked" in the call to "then()"
{"event": "executeEnd", "executeID": 0 } // main program body is ending
{"event": "executeBegin", "executeID": 4, "readyID":2 } // callback f1() is now starting
// resolving promise
{"event": "ready", "executeID":4, "linkID": 3, "readyID": 5} // promise p is now resolved, allowing the "then(function f2()..." to proceed
{"event": "executeEnd", "executeID": 4 } // callback f1() is ending
{"event": "executeBegin", "executeID": 6, "readyID":5 } // callback f2() is now starting
// resolving promise
{"event": "executeEnd", "executeID": 6 } // callback f1() is ending
Events Produce the Async Call Graph
The events above allow us to produce a Directed Acyclic Graph (DAG)
that we call the “Async Call Graph”. Specifically, the executeBegin, link, and ready events correspond to node & edge creation in the graph.
Examples & Visualizations
This all much easier to visualize. We have a list of examples along with step-through visualizations:
- simplePromise - Shows a simple promise example’s execution.
- simpleExpress - Shows a simple express app’s execution.
- expressMiddleware - Shows express app with middleware being used.
- setInterval - Illustrates a call to setInterval.
- lazilyInitializedPromise - Illustrates an express app with a promise that is created & resolved in one request’s context, and “then’d” in other requests’ context.
- markdown-example-1 - Shows example 1 from the Async Context Definitions document
- markdown-example-2 - Shows example 2 from the Async Context Definitionsdocument