Check conditions concurrently

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 which is able to evaluate a variable number of possibly asynchronous functions over a value with in a concurrent way.

const checkConditions = (...predicates) => (value) =>
  Promise.all(predicates.map((p) => p(value))).then((results) =>
    results.reduce((acc, value) => acc && value, true)
  );

or, actually, taking advantage of native array (list) functions, the same can be rewritten as:

const idempotence = (x) => x;
const checkEveryCondition = (...predicates) => (value) =>
  Promise.all(predicates.map((p) => p(value))).then((results) =>
    results.every(idempotence)
  );

Description

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 achieved by a mapping over the predicate functions
  • The mapping 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 a try/catch if using async/await.

Examples

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;
// when calling, given the function predicates are synchronous, they will run sequentally
const divBy2and3and5SyncCheck = checkConditions(divBy2, divBy3, divBy5);

// 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));
// when calling, given the function predicates are async, the functions will run concurrentlly
const divBy2and3and5AsyncCheck = checkConditions(
  divBy2Async,
  divBy3Async,
  divBy5Async
);

const mixedCheck = checkConditions(divBy2, divBy5Async);

Calls are done in a similar way:

const isDivBy2and3and5Sync = await divBy2and3and5AsyncCheck(10); // false
const isDivBy2and3and5ASync = await divBy2and3and5AsyncCheck(30); // true
const isDivBy2and5 = 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 isNumAndDivBy2 = checkConditions(isaNumberAsync, divBy2Async);

// using await
try {
  const result = await isNumAndDivBy2(29);
  console.log(result);
} catch (err) {
  console.error("Process error");
}

// or using promises
isNumAndDivBy2(29)
  .then((result) => console.log(`Process result: ${result}`))
  .catch((err) => console.error("Process error"));
November 18, 2020 - author unknown