Yield and Async notations
The yield
and async
notations aim to improve the code readability in asynchronous JavaScript and iterators. In this article, we attempt to present these notations through the concepts of monad and continuation. From there we show how yield
and async
are closely related.
Yield and Async
We quickly recall how yield
and async
work on some examples.
The notation yield
allows to write generators. The generators are special functions which can pause their execution on a yield
and resume on a .next()
. We can also compose generators with yield*
:
The notation async
helps to write asynchronous operations in a synchronous-like manner. For example we can sequence API calls using the await
keyword:
Monad
Let us dive into some definitions. A monad is a data structure representing some computations. These computations are usually non-purely functional (meaning that they do side-effects).
The main monadic operator is bind
and executes two computations in sequence. The promises are a well-used form of monad. The sequencing operator of the promises is .then
:
Now you know what a monad is!
Continuation
The continuation of an expression is a function representing which computations remain to be done to terminate the current function (or program). For example in:
the continuation of await firstApiCall(param)
to terminate the function foo
is the code represented in green:
which expressed as a function becomes:
Putting everything together
Let us draw a parallel between the yield
and async
notations as follows:
yield notation |
async notation |
|
---|---|---|
sequencing | yield* |
await |
primitive operator | yield |
new Promise |
We sequence generators with the keyword yield*
and promises with await
. Using our previous definitions, we can say that:
yield*
is a syntactic sugar to apply the monadic sequencing operator of the generators to the current continuation;await
is a syntactic sugar to apply the monadic sequencing operator of the promises to the current continuation.
If we take back our async
example:
we can desugar the first await
into a .then
applied to the continuation:
If we desugar the second await
we get:
which is what we would write without the async
notation.
The power of the yield
The operators yield*
and await
use the current continuation at compilation time, as we can view them as syntactic sugars 1. In constrast, the yield
operator gives access to the continuation at run time. We apply the current continuation returned by yield
with the .next
method:
In particular, we can implement the async
notation at run time with the yield
, by doing a .then
with the current continuation returned by the .next
. This is exactly what some libraries like co
do. By applying the same technique, we can actually implement an async
-like notation for most monads thanks to the yield
operator 2. For the fun of it, we give a full example in this JSBin for the state monad.
To summarize
The notations yield
and async
simplify the definition of impure functions. The key operators are yield*
and await
which sequences two computations. More precisely, these operators apply the monadic sequencing combinator bind
to the current continuation of an expression.
The functions async
create promises, but the generators are more generic. Indeed, at runtime, a generator can be interpreted to any side effect thanks to the yield
operator which returns the current continuation.
Comments