thi-ng/umbrella

View on GitHub
packages/compose/src/thread-last.ts

Summary

Maintainability
A
30 mins
Test Coverage
import type { FnAny, FnAnyA, MaybePromise } from "@thi.ng/api";

/**
 * Similar to {@link threadFirst}. A dataflow operator to improve the legibility
 * of long (or deeply nested) call expressions. Takes an `init` value and a
 * number of functions and/or function tuples, consisting of: `[fn, ...args]`.
 * Executes each function (or tuple) with the return value of the previous
 * step/function inserted as **last** argument, using `init` as the first
 * expression. Returns result of last function/step given.
 *
 * @remarks
 * This operator allows the code to be read more easily in the order of
 * execution (same as the `->>` operator/macro in Clojure).
 *
 * @example
 * ```ts tangle:../export/thread-last.ts
 * import { threadLast } from "@thi.ng/compose";
 *
 * const neg = (x) => -x;
 * const sub = (a, b) => a - b;
 * const div = (a, b) => a / b;
 *
 * // without operator: 20 / (10 - (-5))
 * console.log(div(20, sub(10, neg(5))));
 * // 1.3333333333333333
 *
 * // rewritten using operator:
 * threadLast(
 *   5,
 *   neg,       // -5
 *   [sub, 10], // 10 - (-5)
 *   [div, 20],  // 20 / (10 - (-5))
 *   console.log
 * );
 * // 1.3333333333333333
 * ```
 *
 * @param init - start value
 * @param fns - functions / S-expressions
 */
export const threadLast = (
    init: any,
    ...fns: (FnAny<any> | [FnAny<any>, ...any[]])[]
) =>
    fns.reduce(
        (acc, expr) =>
            typeof expr === "function"
                ? expr(acc)
                : expr[0](...expr.slice(1), acc),
        init
    );

/**
 * Async version of {@link threadLast}.
 *
 * @remarks
 * Also see {@link threadFirstAsync}.
 *
 * @param init
 * @param fns
 */
export const threadLastAsync = async (
    init: MaybePromise<any>,
    ...fns: (FnAnyA<any> | [FnAnyA<any>, ...any[]])[]
) => {
    let res = await init;
    for (let expr of fns) {
        res = await (typeof expr === "function"
            ? expr(res)
            : expr[0](...expr.slice(1), res));
    }
    return res;
};