utils.js

/**
 * @module Utils
 * @description A set of utilities
 */

/**
 * Sum the values of the array (must be numbers).
 *
 * @static
 * @method sum
 *
 * @param array {Array} array of number values
 * @return {Number} the sum of the values in the array
 */
export function sum(array) {
  return (array || []).reduce(function (memo, num) {
    if (typeof num === "number") {
      memo += num;
    }
    return memo;
  }, 0);
}

export function is(obj, type) {
  return Object.prototype.toString.call(obj) === "[object " + type + "]";
}

const interestingValues = (entry) =>
  typeof entry[1] !== "undefined" && entry[1] !== "undefined" && entry[1] !== Number.MAX_VALUE;

const formatEntry = (entry) => `${entry[0]} = ${entry[1]}`;

const formatObject = (entry) =>
  typeof entry[1] === "object"
    ? [entry[0], `{${entry[1]?.type ? entry[1].type : "object"}}`]
    : entry;

// This displays well in Firefox, it doesn’t add much on Chrome
class Logger {
  #enabled = true;
  #name = [];
  constructor() {}
  enable() {
    this.#enabled = true;
  }
  disable() {
    this.#enabled = false;
  }
  start(name, params = {}) {
    if (!this.#enabled) return;
    this.#name.push(name);
    const paramsString = Object.entries(params)
      .filter(interestingValues)
      .map(formatObject)
      .map(formatEntry)
      .join(", ");
    console.groupCollapsed(`${name}: ${paramsString}`);
  }
  end(obj) {
    if (!this.#enabled) return obj;
    console.groupEnd();
    if (typeof obj === "string") {
      console.log("%c" + obj, "color: #2289B6");
    } else {
      console.log(obj);
    }
    return obj;
  }
}
export const logger = new Logger();

/**
 * Yields control back to the event loop as a macrotask (not just a microtask), so that
 * anything else already queued to run — e.g. a Comlink call waiting on the same worker's
 * message queue — gets a chance to execute before the caller resumes. Works identically in
 * a browser Worker and a Node worker_threads Worker, since both implement setTimeout.
 *
 * @returns {Promise<void>}
 */
export function yieldToEventLoop() {
  return new Promise((resolve) => setTimeout(resolve, 0));
}

/**
 * The idiomatic way of trying something repeatedly until the outcome is acceptable can be
 * awkard to express (do/while loop), so this converts it into something that is easier to
 * read and understand. There is a counter to break out of accidental infinite loops.
 *
 * @param {Function} generationFunc a function that returns a value
 * @param {Function} testFunc that returns true if result is acceptable, false otherwise
 * @returns {*} the final result
 */
export function tryUntil(generationFunc, testFunc) {
  let num = 0;
  do {
    if (num > 50) {
      console.error("tryUntil could not find result in 50 iterations");
      return generationFunc();
    }
    num++;
    var result = generationFunc();
  } while (!testFunc(result));
  return result;
}