So in JavaScript we now have promises, async/await, babelify etc. And I have a problem with these. We still can't cleanly separate pure and impure functions.
We want a clean separation of pure and impure functions. In JavaScript your impure functions are callbacks (mostly). A typical scenario -
- Handle Post request - Impure function
- Validate Post Input - Pure function
- Read from DB with input - Impure function
- Handle DB error - Pure function
- Do some validation - Pure function
- Create response and send - Pure function
Typically all the six functions above would have been one impure callback function (callback hell). IO^2 will help you split monolothic callbacks into bite size pieces. Bonus, your pure functions can be easily tested. Another bonus, you write code the way you reason about your program.
const IO = require('io-square')
const express = require('express')
const app = express()
new IO(callback => app.post('/', callback)) // Impure Function
.reject((req, res) => { // Pure Function
if (!req.body.email) {
res.redirect('/')
return null
}
return [req, res]
})
.bind(req => new IO(callback => client.hget(req.body.email, callback))) // Impure function
.error(err => { // Pure function
res.send({error: true, message: 'Could not get user record from db'})
})
.reject((req, res, reply) => { // Pure function
if (reply.authorised !== true) {
res.send({error: true, message: 'Not authorised'})
return null
}
return [req, res]
})
.then((req, res) => { // Pure function
// do something
req.authSession.email = req.body.email
res.send({success: true})
})
$ npm install io-square --save
new IO(callback => app.post('/', callback))
Create an instance of an IO Object. Provide the constructor with an io-function. This function should take a result function as the only argument. In the asynchronous call, pass the result function ass the callback.
.error(err => { // Pure function
res.send({error: true, message: 'Could not get user record from db'})
})
.reject((req, res) => {
if (!req.body.email) {
res.redirect('/')
return null
}
return [req, res]
})
- reject - will stop propagation if the pure function given returns null. Otherwise passes on the value(s) returned as arguments to the next method. Multiple value should be returned in an Array.
- map - will take a set of values, modify them, and passes on a new set of values to the next method called.
- bind - is used to bind another asynchronous (nested) function to the data flow. It takes a function whose arguments are the values passed and whose return value is a new IO instance. It will pass a new set of arguments to the next method. The original args passed to it + the arguments passed to the new IO instance callback. Look at this carefully in the bind example above.
- then - is the final method you must always call. This will activate the whole flow. then cannot be called multiple times. It is always the final call.