Right. So in JavaScript we now have promises, async/await, babelify etc. And I have a problem with these. If you don't have a problem with these, then probably, there is not much here for you.
I 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
.reject((req, res, error, reply) => { // Pure function
if (error) {
res.send({error: true, message: 'Could not get user record from db'})
return null
}
return [req, res, reply]
})
.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 a function. This function should take a callback as the only argument. In the function body, make your asynchronous call, and pass it the callback.
.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 values returned in an array, as arguments to the next method.
- 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.