Asynchronous functional javascript using either-async
source fiddle: Asynchronous functional javascript using either-async
In my opinion, by using just two functional patterns: Maybe and Either we can dramatically improve the quality of our code and reap the majority of benefits of a Functional programming style
Using Either to replace try/catch blocks is part of the natural progression of JavaScript (and every object-oriented language)
In this realistic example, I am going to display the scenario where we want to find an employee assigned to a client. The only prerequisite in order to follow the article is to be aware of the Either monad functional pattern. You can find more here :
Either Monad - A functional approach to Error handling in JS
let us see the repository for the clients first
var clientRepository = ({ getById: (id) => mockFetchAsync() .toEither() .bind(response => response .bind(data => data.filter(c => c.id == id).safeHead() .toEither(`there is no client with id ${id}`))), });
a lot of stuff happening here.
- The mockFetchAsync returns a new Promise() of an array
var mockFetchAsync = new Promise(resolve => { setTimeout(() => { resolve( [{ id: 1, name: 'Joan', age: 29, employeeId: 1 }, { id: 2, name: 'Rick', age: 25, employeeId: 3 }] ) }, 1000); });
After mockFetchAsync().toEither() this promise is now transformed into an EitherAsync type this exposes the exact same methods of an Either. This will potentially result in an array or an error. EitherAsync is lazy, and it won't evaluate even after the Promise has finished execution. That’s until we use the cata method to fold the EitherAsync
eitherAsync .cata({ ok: data ?=> console.log("result: " + =>), error: error => console.log("error: " + error) });
From now on we can forget that we are working asynchronously. We just assume we have a synchronous Either. The next step is to get the first element of the array that matches the id , we are going to use the filter and the safeHead that return a maybe ([_]) (learn more about maybe here)
response.filter(c => c.id == id).safeHead() Array.prototype.safeHead = function () { return this.length > 0 ? some(this[0]) : none() }
we can make a Maybe into an either by providing a default message if there where no clients found
data .filter(c => c.id == id) .safeHead() .toEither(`there is no client with id ${id}`))
now since this is an either we must use bind and not map in order to access the result of the fetch since this is also an Either. So this whole block finally becomes
getById: (id) => mockFetchAsync() .toEither() .bind(response => response .bind(data => data.filter(c => c.id == id) .safeHead() .toEither(`there is no client with id ${id}`))),
beautiful !!
Try to write the same using try/catch and async/await.
Now for the second part, we are going to have a similar Employee repository call and get an Either[employee]
var employeeRepository = ({ getById: (id) => mockFetchAsync() .toEither() .bind(response => response.filter(c => c.id == id) .safeHead() .toEither(`there is no employee with id ${id}`)) });
then we are going to use again bind to Either[Client] and Either[Employee] and get the final result :
var getAssignedEmployeeForCustomerWithId = customerId => clientRepository.getById(customerId) .bind(client => employeeRepository.getById(client.employeeId)) .map(e => e.name); getAssignedEmployeeForCustomerWithId(1) //Display using cata .cata({ ok: name => console.log("assigned Employee name: " + name), error: error => console.log("error: " + error) });