⏳❓ 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.
| Signature | Description |
|---|---|
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.
| Signature | Description |
|---|---|
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.
| Signature | Description |
|---|---|
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
Pathinput, returning multivalued results. timeoutMs→ Adds a timeout guard; rejects withTimeoutErrorif exceeded.i, value→ Partial application; binds thei-th argument.nAttempts, baseDelay, factor, maxDelay→ Retry mode; allows repeated execution with exponential backoff. The returned AsyncWhat has a.stoppedproperty to cancel retries.names, name→ Nominal mode; maps object properties to arguments and optionally stores the result.
| Signature | Description |
|---|---|
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
Post a Comment