Last active
February 10, 2017 23:06
-
-
Save Zodiase/336b03be202aaaf71437cc665928521d to your computer and use it in GitHub Desktop.
Functional JavaScript with Ramda
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Runs a sub composition that is invisible to its parent composition. | |
* @param {...Function} funcs | |
* @return {a -> Promise(a)} | |
*/ | |
const spyPipeP = (...funcs) => (val) => R.partialRight(R.pipeP, [() => val])(...funcs)(val); | |
/** | |
* Runs a sub composition that is invisible to its parent composition. | |
* @param {...Function} funcs | |
* @return {a -> Promise(a)} | |
*/ | |
const spyComposeP = (...funcs) => (val) => R.partial(R.composeP, [() => val])(...funcs)(val); | |
/** | |
* Returns a promise function that sleeps for the given time and then resolves with the input value. | |
* Effectively invisible in a composition. | |
*/ | |
const sleep = (ms) => (val) => { | |
return new Promise((resolve, reject) => { | |
try { | |
setTimeout(R.partial(resolve, [val]), ms); | |
} catch (error) { | |
reject(error); | |
} | |
}); | |
}; | |
/** | |
* Spy on the data in the pipe and run the given function without changing the pipe data. | |
* @param {Function} func | |
* The function to run. It is passed the pipe data as the only argument. Whatever it | |
* returns is discarded. | |
* @return {a -> a} | |
*/ | |
const spy = (func) => R.partial(R.tap, [func]); | |
// Raw implementation. | |
const spy = (func) => { | |
return (val) => { | |
func(val); | |
return val; | |
}; | |
}; | |
/** | |
* The promise based version of `spy`. | |
* @param {Function} func | |
* The function to run. If it returns a Promise, the spying promise resolves when the | |
* returned promise is settled (no matter it's resolved or rejected). Otherwise the spying | |
* promise resolves immediately. | |
* @return {a -> Promise(a)} | |
*/ | |
const spyP = (func) => { | |
return (val) => new Promise(resolve => { | |
const result = func(val), | |
finalResolve = () => resolve(val); | |
if (result instanceof Promise) { | |
result.then(finalResolve, finalResolve); | |
} else { | |
resolve(val); | |
} | |
}); | |
}; | |
/** | |
* Converts a promise based function to a callback based one. | |
*/ | |
const promiseToCallback = (funcP) => (...args) => { | |
// The last one should be the callback, while the rest are the arguments. | |
const callback = args.pop(); | |
try { | |
const result = funcP(...args); | |
if (result instanceof Promise) { | |
result.then( | |
(result) => callback(null, result), | |
(reason) => callback(reason) | |
); | |
} else { | |
callback(null, result); | |
} | |
} catch (error) { | |
callback(error); | |
} | |
}; | |
/** | |
* Promise based for-each loop. | |
* If the given function returns a promise, the loop waits for it to settle before | |
* iterating on to the next item when the promise is resolved or breaking itself when the | |
* promise is rejected. Otherwise the loop iterates to the next item immediately. | |
* In any case, this is always an async process. | |
* @param {a -> Promise(*)|a -> *} funcP | |
* @param {Array.<*>} items | |
* @return {Promise} | |
*/ | |
const forEachP = (funcP, items) => new Promise((resolve, reject) => { | |
async.eachSeries(items, promiseToCallback(funcP), (error) => { | |
if (error) { | |
reject(error); | |
} else { | |
resolve(); | |
} | |
}) | |
}); | |
// Raw implementation. | |
const forEachP = (funcP, items) => { | |
if (items.length === 0) { | |
return Promise.resolve(); | |
} else { | |
const [head, ...tail] = items; | |
return new Promise((resolve, reject) => { | |
try { | |
const headResult = funcP(head); | |
if (headResult instanceof Promise) { | |
headResult.then( | |
// In for-each loops, we don't care about return values. | |
() => resolve(), | |
// But we care about exceptions. | |
(reason) => reject(reason) | |
); | |
} else { | |
// In for-each loops, we don't care about return values. | |
resolve(); | |
} | |
} catch (error) { | |
// But we care about exceptions. | |
reject(error); | |
} | |
}) | |
// Set up loop-next. | |
.then( | |
() => forEachP(funcP, tail), | |
(reason) => Promise.reject(reason) | |
); | |
} | |
}; | |
const rejectP = (predicate, filterable) => new Promise((resolve, reject) => { | |
async.rejectSeries(filterable, promiseToCallback(predicate), (error, results) => { | |
if (error) { | |
reject(error); | |
} else { | |
resolve(results); | |
} | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment