Mar 07 2020

From Promises to Fluture

Today I was a little adventurous and decided to look into alternatives to JavaScript Promises. They are a fine pattern, but I thought it would be cool if I added some functional to the mix.

I came across a package called Fluture. Fluture complies with the Fantasy Land Spec. If you’re into functional programming (and like JavaScript), you should check it out if you haven’t already.

A bit of code that I ported over was a simple HTTP GET request which returns JSON to the user. Since side-effects are introduced from the database call, we must use some strategy that encapsulates the asynchronous processing.

Prior to moving towards a more declarative approach, I had the implementation:

exports.contracts.api.get = async (req, res) => {
  const { id } = _.get(req, 'params', {})
  const contract = await Arrangement.fetchContractWithRequirements({
    _id: id
  })
  if (contract) {
    return res.json(Arrangement.JAContract(Arrangement.Contract(contract)))
  } else {
    return res.status(404).send()
  }
}

First I had to determine how to encapsulate promises. The documentation had examples where encaseP was used. After that, it was a matter of mapping and forking the final result.

exports.contracts.api.get = async (req, res) => Some(req.params.id).map(
  (_id) => Future.encaseP(Arrangement.fetchContractWithRequirements)({_id})
  .pipe(Future.map(Arrangement.Contract))
  .pipe(Future.map(Arrangement.JAContract))
  .pipe(Future.fork(() => res.status(404).send())((d) => res.json(d)))
)

One thing that I had not realized is that the Future pipeline is lazy evaluated. This means that the code will not execute until a .fork() is added to the end of the pipeline. You can read more about it on the Fluture wiki.

One benefit of declarative code is that it’s easy to know what is going on with a quick glance. Another benefit is that the last three statements are clearly deterministic. This means that defects will probably reside in the promise code. In the case that bugs are fixed in the deterministic area, we can easily test the scenario to ensure that the fix will last forever.

One final benefit is that there is a reduction in lines of code. When it comes to controller logic, the less code the better. I don’t care so much about bulky models in comparison.

Despite all of these benefits, a clear drawback is the difficulty in adding diagnostics to this large chain. You would be required to modify the code significantly just to inspect whats going on.

All in all, I think the refactor is here to stay.