Async await like syntax, without ppx in Rescript !!!
Tags: rescript, reason, functional, promise
Published: 2021-09-29 15:01:13

### Prerequisite:

- Basic understanding of functional programming.
- Basic knowledge on Rescript/ReasonML.

The code and ideas that I will be discussing about in this article are my own opinions. It doesn't mean this _is_ the way to do it, but it just means that this is _also_ a way to do it. Just my own way.

In Rescript, currently (at the time of writing this article) there is no support for async/await style syntax for using promises. You can read about it [here](https://rescript-lang.org/docs/manual/latest/promise). Even though Rescript's pipe syntax make things cleaner and more readable, when it comes to working with promises there is still readability issues due to the lack of async/await syntax. There are ppx available to overcome this issue. But what if, we can overcome this issue without using any ppx.

Lets first look at, how existing promise chaining looks like in Rescript.

```reasonml
fetchAuthorById(1)
  |> Js.Promise.then_(author => {
    fetchBooksByAuthor(author) |> Js.Promise.then_(books => (author, books))
  })
  |> Js.Promise.then_(((author, books)) => doSomethingWithBothAuthorAndBooks(author, books))

```

In the above code, to access both author and books, I am creating a tuple to be passed into the next promise chain and using it there. This can easily grow and become more cumbersome when we chain three or more levels.

The idea is to,

> _Create a function that takes multiple promise functions as labelled arguments, executes them sequentially and stores each result as a value in an object, with labels as keys of the object_

This idea is inspired from Haskell's `DO` notation. Lets see, how this function looks like.

```reasonml
type promiseFn<'a, +'b> = 'a => Js.Promise.t<'b>

let asyncSequence = (~a: promiseFn<unit, 'a>, ~b: promiseFn<{"a": 'a}, 'b>) =>
  a()
  |> Js.Promise.then_(ar => {"a": ar}->Js.Promise.resolve)
  |> Js.Promise.then_(ar =>
    ar
    ->b
    ->map(br =>
      {
        "a": ar["a"],
        "b": br,
      }
    )
  )
```

Lets understand what this function is doing.

1. A `type` called `promiseFn` is defined, that takes some polymorphic type `'a` and returns a promise of type `'b`.
1. `asyncSequence` function takes two labelled arguments `a` and `b` which are of type `promiseFn`.
1. Argument `a` is a function that takes nothing, but returns a promise of `'a`.
1. Argument `b` is a function that takes an `Object` of type `{"a": 'a}` where the key `a` corresponds to the label `a` and the value `'a` corresponds to the response of the function `a`.
1. `a` is first invoked and from its response an `Object` of type `{"a": 'a}` is created and passed into function `b`. The response of function `b` is taken and an object of type `{"a": 'a, "b": 'b}` is created.

The above function, chains only 2 promise functions. But, using this method we can create functions that chains multiple promise functions.

```reasonml
// Takes 3 functions
let asyncSequence3 = (
  ~a: promiseFn<unit, 'a>,
  ~b: promiseFn<{"a": 'a}, 'b>,
  ~c: promiseFn<{"a": 'a, "b": 'b}, 'c>,
) =>
  asyncSequence(~a, ~b) |> Js.Promise.then_(abr =>
    abr->c
      |> Js.Promise.then_(cr =>
        {
          "a": abr["a"],
          "b": abr["b"],
          "c": cr,
        }->Js.Promise.resolve
      )
  )

// Takes 4 functions
let asyncSequence4 = (
  ~a: promiseFn<unit, 'a>,
  ~b: promiseFn<{"a": 'a}, 'b>,
  ~c: promiseFn<{"a": 'a, "b": 'b}, 'c>,
  ~d: promiseFn<{"a": 'a, "b": 'b, "c": 'c}, 'd>,
) =>
  asyncSequence3(~a, ~b, ~c) |> Js.Promise.then_(abcr =>
    abcr->d
      |> Js.Promise.then_(dr =>
        {
          "a": abcr["a"],
          "b": abcr["b"],
          "c": abcr["c"],
          "d": dr,
        }->Js.Promise.resolve
      )
  )

// .... Any level
```

See, we are using previous `asyncSequence3` to define next level `asyncSequence4`. To understand this function better, lets see how it is used. Lets rewrite our previous example using this `asyncSequence4`.

```reasonml
asyncSequence4(
  ~a=() => fetchAuthorById(1),
  ~b=arg => fetchBooksByAuthor(arg["a"]),
  ~c=arg => doSomethingWithBothAuthorAndBooks(arg["a"], arg["b"]),
  ~d=arg => Js.log(arg)->Js.Promise.resolve
)

// Response of asyncSequence4 will be a promise of type
// {
//  "a": <Author>,
//  "b": <BooksArray>,
//  "c": <Response of doSomethingWithBothAuthorAndBooks>
//  "d": <unit, since Js.log returns unit>
// }
```

What is happening is, the response of `fetchAuthorById` is taken and an object of type `{"a": <Author>}` is created. This object is passed to function `b` as `arg` and hence that function `b` has access to previous function `a`'s result. Now the response of `b` is merged together with response of `a` into a single object as `{"a": <Author>, "b": <BooksArray>}` and passed to function `c` as argument `arg`. Now function `c` has access to both the response of `a` as well as response of `b` in the object that is received as argument. This is continued down the path to function `d`.

With this approach the chaining is easy and multiple `asyncSequence` can be chained like below, which can provide access to all the previous values.

```reasonml
let promiseResp = asyncSequence4(
  ~a=() => fetchAuthorById(1),
  ~b=arg => fetchBooksByAuthor(arg["a"]),
  ~c=arg => doSomethingWithBothAuthorAndBooks(arg["a"], arg["b"]),
  ~d=arg => Js.log(arg)->Js.Promise.resolve
)

asyncSequence(
  ~a=() => promiseResp,
  ~b=arg => doSomethingWithAllThePreviousResponse(arg["a"])
)
```

Before we jump into pros and cons of this approach, lets see one common mistake that can happen.

```reasonml
asyncSequence3(
  ~a=() => firstExecution(),
  ~c=_ => thirdExecution(),
  ~b=_ => secondExecution(),
)
```

The above code will compile fine. It is easy to think that `c` will be executed after `a`, but thats not true.
The execution will always happen from `a` to `z` even though the order is changed.

Now, lets see what are the pros and cons of this approach.

## Pros:

1. Far more readable than the raw Promise chaining.
1. Somewhat similar to the js async await syntax.
1. Each function down the line has access to all the previous responses.
1. No PPX and no additional dependencies needed.
1. Completely type safe. Compiler will raise errors of any wrong usage.
1. One `asyncSequence` can be chained to the next `asyncSequence` easily.

## Cons:

1. Multiple overloaded functions required.
1. Order must not be changed.
1. Keys of the object cannot be changed ("a", "b" ... will always be the keys).

You can check the refactored, full code [here](https://github.com/praveen-kumar-rr/rescript-async/blob/master/src/Promise.res).

**Hope you enjoyed! Happy Hacking!**