(inc altitude)

"Using Sweet.js to Introduce Delimited Continuation to JavaScript"

#inst "3914-02-10T15:00:00.000-00:00"
"ympbyc"
"

Pyramid of Doom

JavaScript programs make heavy use of asynchronous functions. Asynchronous functions in JavaScript are achieved by practicing what's called Continuation Passing Style (CPS) where asynchronous functions are applied to callback functions. Traditionally, these programs suffered from deep nestings of callback functions due to the lack of syntactic support. Those nests of callback functions are sometimes called Pyramid of Doom or Callback Hell.

To solve the issue, programmers started to use constructs known as promises or futures. Using promises, JavaScript codes became Delimited Continuation Passing Style. In which nesting don't occur, but the number of callbacks stays the same as before.

In this post, I present a better solution. I created macros using sweet.js that implements shift and reset, two operators that are used to capture delimited continuations.

Spec

Sweet.js provides some ways to define postfix macros. However, It is impossible to capture continuations of expressions such as reset(f(g(shift(k=>...)))) with a postfix macro. Due to this restriction, shift shall be implemented as a statement construct. This way, the macro only has to look at statements that follow the shift statement.

reset {
  ...
  shift_let <var> = <function>
  <continuation>...
}

shift_let takes <function> and give it the continuation. When the continuation gets invoked with a value, the value gets bound to <var>.

Implementation

macro reset {
  rule { {$exprs...} } => {
    (function () { $exprs... })()
  }
}

macro shift_let {
  rule { $var = $fn $rest...} => {
    $fn(function ($var) { $rest... })
  }
}

Usage

reset {
  shift_let name = getName
  shift_let data  = (function (continuation) {
      $.getJSON('http://.../users/' + name, continuation)
  })
  console.log(name)
  console.log(data)
}

//ヘルパ
function getName (continuation) {
  $('#some-input').on('change', function () {
    continuation($(this).val())
  })
}

Notice how asynchronous operations are written flat with no use of callbacks.

"