i'm trying to compose some functions together:
compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));
checkAuthorization
returns a promise that check if a user is authorized.
buildParams
receives someRequestData
, and pipes the result to searchItem
.
checkAuthorization()
.then(() => {
compose(
searchItem,
buildParams
)(someRequestData)
}, (e) => {
handleError(e)
})
I think it's OK, but I wish to have a more elegant look for readability, something like:
compose(
searchItem,
checkAuthorization
buildParams
)(someRequestData)
so what will happen is:
1) build params
2) checkAuth
3) search item
Any suggestions?
No, that's not possible, since checkAuthorisation
does not receive and pass through the params. And even if you would rewrite it to do that, it still would be weird and a reader would assume that you're building the params whose authorisation should be checked. So don't do that - you have a non-linear flow, and trying to force it into some linear composition is no good.
Btw, I would recommend to avoid compose
when you're calling the function immediately anyway:
checkAuthorization().then(() =>
searchItem(buildParams(someRequestData))
, e =>
handleError(e)
);
or maybe
checkAuthorization().then( compose(searchItem, buildParams, ()=>someRequestData)
, handleError ); // ^^^^ "const"
Here's a composer to handle both sync functions and Promises. Looks like it works correctly maintaining the order:
// Async pipe try. Pass functions left to right
const pipePromises = (...fns) => x => fns.reduce((p, fn) => p.then(fn), Promise.resolve(x));
// functions for the test
const me = x => new Promise(function(resolve, reject) {
setTimeout(() => resolve(x), 10)
})
const double = x => new Promise(function(resolve, reject) {
setTimeout(() => resolve(x * 2), 30)
})
const inc = x => new Promise(function(resolve, reject) {
setTimeout(() => resolve(x + 1), 400)
})
const log = x => { console.log('log: ', x); return x }
const syncTriple = x => x * 3; // sync function
// let's call our chain
pipePromises(
me, log, // 3
double, log, // 6
syncTriple, log, // 18 -- SYNC
inc, log, // 19
double, log, // 38
inc, log, // 39
syncTriple, log, // 117 -- SYNC
inc, log // 118
)(3) // 3
I just made an npm module to handle elegant Promise
composition.
It's still in early stage, but you're welcome to check out the code and change it as it fits your needs and standards.
Basically it offers two methods which might meet your needs:
Combine
With Promise.combine({...})
you can combine several Promises by providing an object with a series of functions returning Promises and accepting the result of previous ones as input like this:
Promise.combine({
item: () => searchItem,
auth: ({item}) => checkAuth,
params: ({item, auth}) => buildParams
}).then(({item, auth, params}) => {
// here you can do what you need
})
Reduce
With Promise.reduce([...])
you can chain Promises in an array of functions returning Promises and accepting as input the output of the previously executed Promise:
Promise.reduce([
() => searchItem,
(item) => checkAuth,
(auth) => buildParams
]).then((params) => {
// here you can do what you need
})
Notice in this case you won't have access to item
in the .then()
function, but you could always compose the result of the checkAuth
Promise in order to pass the item
downstream as well:
Promise.reduce([
() => searchItem,
(item) => checkAuth.then((auth) => {
return {auth, item}
}),
({auth, item}) => buildParams.then((params) => {
return {params, item}
}),
]).then(({params, item}) => {
// here you can do what you need
})
Input
You can also add some input data from the request like this:
Promise.reduce([
(requestData) => searchItem,
(item) => checkAuth,
(auth) => buildParams
], requestData).then((params) => {
// here you can do what you need
})
See I passed the requestData
as second parameter of Promise.reduce([...], requestData)
and it gets passed as parameter to the first function.
Here you can see the functions code.
Hope this helps.