⏳❓ AsyncWhat Class

⏳❓ AsyncWhat Class — Asynchronous Functional Programming with @fizzwiz/fluent

The AsyncWhat class provides a fully asynchronous counterpart to What. It wraps functions and values into async-compatible, chainable constructs, enabling fluent and lazy functional composition in the async domain.

๐Ÿ”ง Instantiation

You can create an AsyncWhat by wrapping a function, value, or another AsyncWhat instance.

SignatureDescription
AsyncWhat.of(...args, value)Returns an AsyncWhat producing value when called with args, otherwise undefined
AsyncWhat.as(f)Converts a function, constant, or Promise to an AsyncWhat

⧉ Snippet: of()

const f = AsyncWhat.of('answer', Promise.resolve(42));
console.log(await f('answer')); // -> 42

⧉ Snippet: as()

const g = AsyncWhat.as(async x => x * x);
console.log(await g(4)); // -> 16

๐ŸŽฏ Function Restriction

AsyncWhat supports asynchronous predicates to filter or restrict values.

SignatureDescription
if(predicate)Filters input asynchronously; returns undefined or throws if predicate fails
which(predicate)Filters output asynchronously; returns undefined or throws if predicate fails

⧉ Snippet: if()

const divide = AsyncWhat.as(async x => 1 / x);
const notZero = async arg => arg !== 0;

const f = divide.if(notZero);
const F = divide.if(notZero, "division by zero");

console.log(f(0)); // -> undefined
console.log(F(0)); // -> throws "division by zero"

⧉ Snippet: which()

const inc = AsyncWhat.as(async x => x + 1);
const positiveResult = async y => y >= 0;

const f = inc.which(positiveResult);
const F = inc.which(positiveResult, "negative result");

console.log(f(-2)); // -> undefined
console.log(F(-2)); // -> throws "negative result"

๐Ÿงฉ Function Composition

Composition methods support sequential chaining, fallback logic, parallel execution, and iteration.

SignatureDescription
sthen(that)Sequential composition: that(await this(arg))
else(that, matcher)Fallback: uses that(arg) if primary returns undefined or throws matching error
match(...those)Parallel composition: [await this(arg), ... those.map(f => await f(arg))]
each(that)Cartesian composition: applies async function that on each result of this

⧉ Snippet: sthen()

const f = AsyncWhat.as(async x => x + 1);
const g = AsyncWhat.as(async x => x * 2);
const h = f.sthen(g);
console.log(await h(3)); // -> 8

⧉ Snippet: else() with error handling

const divide = AsyncWhat.as(async x => 1 / x);
const notZero = async arg => arg !== 0;

const f = divide
  .if(notZero, "division by zero")
  .else(x => Infinity, /division by zero/);

console.log(await f(0));  // Infinity
console.log(await f(1));  // -> 1

⧉ Snippet: match()

const f = AsyncWhat.as(async x => x + 1);
const g = async x => x * 2;
const h = f.match(g);
console.log(await h(3)); // -> [4, 6]

⧉ Snippet: each()

const f = AsyncWhat.as(async x => [x, x + 1]);
const g = async y => [y, y * 2];
const h = f.each(g);
console.log(await h(2));  // -> [2, 4, 3, 6]

๐Ÿ› ️ Transformation via when(predicate, emitter, event, timeout, start)

The when() method allows an AsyncWhat to react to events and optionally control cyclic or self-driven computations. It always produces a new AsyncWhat that executes asynchronously when the specified event occurs and the predicate matches.

  • start = true → Executes the underlying AsyncWhat when the predicate evaluates truthy.
  • start = false → Immediately invokes the underlying AsyncWhat (so it can run and be stopped) and stops it when the predicate evaluates truthy.

The returned AsyncWhat proxies the .stopped property of the underlying AsyncWhat, allowing control over retries or cyclic operations.

⧉ Snippet: when() with WebSocket messages

// Assume an open WebSocket connection
const ws = new WebSocket("wss://chat.example.com");

const requestId = "123";

// Step 1: message sender with a unique requestId
const sendMessage = () =>
  ws.send(JSON.stringify({
    user: "Me",
    requestId,
    message: "Hello Alice!"
  }));

// Step 2: Alice's reply handler
const handleReply = What.as(evt => {
  const msg = JSON.parse(evt.data);
  console.log("๐Ÿ’ฌ Got reply:", msg.message);
});

// Step 3: predicate to select Alice’s response by requestId
const isResponse = evt => {
  const msg = JSON.parse(evt.data);
  return msg.user === "Alice" && msg.requestId === requestId;
};

// Step 4: waiter for Alice’s reply, within 5s
const waitForAlice = handleReply
  .when(isResponse, ws, "message", 5000)  // implicitely lift sync What into AsyncWhat
  .else(() => console.log("⏰ Alice didn’t reply in time"), "TimeoutError")  // async chain
  .else(() => console.log("⚠️ Connection error"));

// Step 5: compose and execute request–response workflow
await AsyncWhat.as(sendMessage)       // lift sync function into AsyncWhat
  .sthen(waitForAlice)();             // async chain

// Outcome depends on Alice:
// - If she replies with: { user: "Alice", requestId: "123", message: "Hi!" }
//     → logs "๐Ÿ’ฌ Got reply: Hi!"
// - Otherwise → logs "⏰ Alice didn’t reply in time"
// - Or if WebSocket errors occur → logs "⚠️ Connection error"

⧉ Snippet: when() (start/stop event-driven triggers)


// Cyclic AsyncWhat
const cyclic = doSomething.self(Infinity, 10, 1);

// Define start/stop triggers
const bounded = cyclic
  .when(isStart, emitter, event, undefined, true)
  .when(isStop, emitter, event, undefined, false);

// Launch bounded AsyncWhat
bounded();

This allows expressive start/stop control for event-driven asynchronous workflows, including infinite retries, user interaction, or distributed events.

๐Ÿ› ️ Transformation via self()

The self() method extends an AsyncWhat for timing, retry, or context-aware execution.

Depending on the arguments, behavior changes:

  • No arguments → Expands over Path input, returning multivalued results.
  • timeoutMs → Adds a timeout guard; rejects with TimeoutError if exceeded.
  • i, value → Partial application; binds the i-th argument.
  • nAttempts, baseDelay, factor, maxDelay → Retry mode; allows repeated execution with exponential backoff. The returned AsyncWhat has a .stopped property to cancel retries.
  • names, name → Nominal mode; maps object properties to arguments and optionally stores the result.
SignatureDescription
self()Expands over Path input, returning multivalued async results
self(timeoutMs)Adds a timeout guard to the computation
self(i, value)Partially applies the i-th argument with value
self(nAttempts, baseDelay, factor, maxDelay)Retries execution with exponential backoff, supports .stopped flag
self(names, name)Maps object fields to arguments; stores result under name if provided

⧉ Snippet: self() (Retry)

let attempts = 0;
const f = AsyncWhat.as(async () => { attempts++; throw new Error('fail'); });
const retryable = f.self(5, 10, 2); // 5 retries, 10ms base, factor 2

try { await retryable(); } catch(e) { console.log('failed after retries'); }
console.log(attempts); // -> 5

⧉ Snippet: self(timeoutMs) (Timeout guard)

const fetchText = AsyncWhat.as(url => fetch(url).then(res => res.text()));
const safeFetch = fetchText.self(2000).else(() => "⚠️ default text");
console.log(await safeFetch("https://example.com"));

⧉ Snippet: self() (Partial Application)

const sum = async (a, b, c) => a + b + c;
const partial = AsyncWhat.as(sum).self(1, 10);
console.log(await partial(2, 3)); // -> 15

⧉ Snippet: self() (Nominal)

const f = AsyncWhat.as(async x => x * 2).self(['a'], 'b');
console.log(await f({ a: 5 })); // -> { a: 5, b: 10 }

Comments

Popular posts from this blog

๐Ÿงฑ v0.0.0-dev.4

๐Ÿ‘… v0.0.0-dev.12 — Streamlining the Library

๐Ÿง  The Power of the when() Method