A system is said to be concurrent if it can support two or more actions in progress at the same time. A system is said to be parallel if it can support two or more actions executing simultaneously.
The Art of concurrency
We come up with a (curried) function with which we are able to evaluate a variable number of possibly asynchronous functions over a value in a concurrent way.
const checkConditions = (...predicates) => (value) =>
Promise.all(predicates.map((p) => p(value))).then((results) => results.reduce((acc, value) => acc && value, true));
The ...predicates
means a variable number of arguments. Calling the function with just predicate functions will produce a function which accepts just a value to apply the functions over.
The body of the function is quite easy to understand:
Promise.all
expects an array of promises, which is achievec by a mapping over the predicate functions- The
map
ping will produce an array of promises, already resolved if the function applied is synchronous, or waiting for being resolved or rejected if they are asynchronous. - In this mapping the asynchronous functions are being executed in a concurrent way, speeding up the process.
- In the
then
block the results of the promises are reduced, resolving to a truthy value only if all the promises were resolved to truthy values. - Remark: when calling this function we'll need a
catch
block if promises are used or enclose the call in atry/catch
if using async/await.
It is possible from the function above set custom conditions based on a list of functions, either synchronous or asynchronous.
// synchronous predicates
const divBy2 = (v) => v % 2 === 0;
const divBy3 = (v) => v % 3 === 0;
const divBy5 = (v) => v % 5 === 0;
const divBy2_3_5_SyncCheck = checkConditions(divBy2, divBy3, divBy5);
// when calling, given the function predicates are synchronous, they will run sequentally
// asynchronous predicates
const divBy2Async = (v) => new Promise((resolve) => setTimeout(() => resolve(v % 2 === 0), 1000));
const divBy3Async = (v) => new Promise((resolve) => setTimeout(() => resolve(v % 3 === 0), 500));
const divBy5Async = (v) => new Promise((resolve) => setTimeout(() => resolve(v % 5 === 0), 1000));
const divBy2_3_5AsyncCheck = checkConditions(divBy2Async, divBy3Async, divBy5Async);
// here, when called, the functions will run concurrentlly
const mixedCheck = checkConditions(divBy2, divBy5Async);
Calls are done in a similar way:
const isDivBy2_3_5Sync = await divBy2_3_5_SyncCheck(10); // false
const isDivBy2_3_5ASync = await divBy2_3_5AsyncCheck(30); // true
const isDivBy2_5 = await mixedCheck(10); // true
But don't forget catching errors
const isaNumberAsync = (v) =>
new Promise((resolve, reject) =>
setTimeout(() => {
if (typeof v === 'number') resolve(true);
else reject(new Error(`Not a number: ${v}`));
}, 1800)
);
const isDivBy2_0 = checkConditions(divBy2Async, isaNumberAsync);
// using await
try {
const result = await isDivBy2_0(29);
} catch (err) {
console.error('Process error');
}
// or using promises
isDivBy2_0(29)
.then((result) => console.log('Process result'))
.catch((err) => console.error('Process error'));