Skip to content

Instantly share code, notes, and snippets.

@Zodiase
Last active February 10, 2017 23:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Zodiase/336b03be202aaaf71437cc665928521d to your computer and use it in GitHub Desktop.
Save Zodiase/336b03be202aaaf71437cc665928521d to your computer and use it in GitHub Desktop.
Functional JavaScript with Ramda
/**
* 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