Implementing async/await in javascript with Sweet.js
Ever since I went back to Javascript, I’ve been missing C#’s async/await system.
With C# you can mark some methods async
. Afterwards, you can call them “synchronously” using the await
keyword. Behind the scenes, the compiler rewrites the code to use callbacks.
For example, C# lets you write something like this:
And it gets compiled to this:
To me, it’s a net win in terms of readability because I’ve never really liked callbacks. They’re an implementation detail and I’d rather have them swept under the rug.
Last week I heard of sweet.js, a macro compiler for Javascript, so I decided to try to implement some sort of await
functionality with macros.
The basics of Sweet.js
Sweet.js lets you define syntaxic sugar, i.e, macros which get expanded at compile time.
A sweet.js macro has two parts, a matching expression and a replacement expression.
Let’s go back to C macros because sweet’s macros will be easier to understand this way. In C, a macro is a statement which defines an expression and a replacement for this expression. During the compilation, the macro is expanded by the preprocessor.
Here’s the simplest macro of all, a greet function.
This is the equivalent sweetjs macro:
There’s several important things.
-
First you can see sweet has a little more syntax than the C preprocessor. A macro is defined inside a
macro
block. Inside this block you can have several rules of the form{ pattern } { replacement }
. -
The rules for matching macros are similar to regexps. The block
($name:lit)
means: “match one litteral between two parentheses and assign it to the variable$name
”. -
The replacement block contains the replacement text and can optionally call javascript code.
So, we’ve got a macro and it gets expanded at compile-time. However, because of the way we wrote it, it will trigger an error if we pass it more than one argument. Let’s fix this.
Handling multiple parameters
Sweetjs has a specific syntax for defining variadic macros: a ...
will match any token similar to the previous match:
Again, here are some important changes:
- We don’t want to match commas, so ask the compiler to ignore them by using
(,)
. - The ‘…’ token itself. The tokens matched are exposed in the replacement rule as
...
.
Now, let’s try to define a small macro to do simplify those messy jQuery ajax calls.
Handling async AJAX calls
The jQuery guys, knowing how painful it is to use callbacks, have defined a promise interface. It allows us to chain calls in a pseudo-procedural type:
I still find this too annoying. Let’s simplify things with this macro:
This compiles to the following javascript:
There’s only two major changes between the greet
macro and this one:
- We’re using variadic parameters differently than in the previous example: instead of capturing function parameters we use them to capture every token after the macro.
- We’re redefining
var
as a macro. This lets us expands constructs likevar result = await $.get
. To get more specific, we’re redefiningvar
as an anonymous macro because we don’t want sweet to try to expand everyvar
inside the macro.
By the way, do you wonder where these weird result$511
and al come from? It’s because sweet macros are hygienic: when they get expanded, identifiers are renamed to prevent name clashes.