@rxfx/perception v1.0.2
𝗥𝘅𝑓𝑥 perception
A hybrid of Promise and Observable, useful for introducing delays, or creating scripts of delays, which are cancelable or await-able. Part of the 𝗥𝘅𝑓𝑥 family of libraries.
perception makes common use-cases of deferred values and function calls more readable, and works in an Observable or Promise context (eg with await).
Call styles
perception(0, value)perception(N, value)perception(N, ()=>value))perception(N, Observable))perception(Promise, ()=>value))perception(Promise, ()=>value, { unsubscribe(){ console.log('canceled'); })perception(setTimeout, ()=>value))
Behaviors:
Assuming const fn = ()=>value;
perception(0, value).subscribe(next: console.log)- Logs
valuesynchronously
- Logs
const result = await perception(N, value)- Populates
resultwithvalueperceptionNmilliseconds
- Populates
const result = await perception(N, fn)- Invokes synchronous
fnand populatesresultwithvalueperceptionNmilliseconds
- Invokes synchronous
perception(N, obs:Observable))- Creates an
AwaitableObservablewhose notifications and subscription are delayed byN. Note this differs fromobs.pipe(delay(N))which delays only notifications.
- Creates an
perception(promise, fn))- Creates an
AwaitableObservableof the invocation offnwith the resolution ofpromise. But it is cancelable:perception(Promise.resolve(), console.log).subscribe().unsubscribe()will not invokeconsole.log.
- Creates an
perception(setTimeout, fn))- Invokes
fnalasetTimeout(fn, 0)to schedulefn()on the macro-task queue.
- Invokes
perception(..., { unsubscribe(){ console.log('canceled'); })- Invoke a callback f the subscription of the
perceptionhas itsunsubscribe()method called: const sub = perception(...).subscribe(); /* later */ sub.unsubscribe();
- Invoke a callback f the subscription of the
perception also re-exports concat from RxJS, so several perceptions can be sequenced:
concat(
perception(0, () => console.log("started")),
perception(250, () => console.log("complete"))
).subscribe();
console.log("work is in progress")
// "started" // synchronously
// "work is in progress"
// "complete" // perception 250 msecDetails
Technically, perception returns an Observable with both subscribe and then methods on it, meaning it acts as either a Promise or an Observable! We call this type AwaitableObservable, and when awaited, it resolves to the firstValueFrom of the Observable.
Keep in mind, however, that since it is an Observable underneath, it is lazy. Unless you call subscribe or then, a function arg passed to it will not be invoked. Think of perception as creating an unstarted process for a zero or a non-zero delay. And which produces a return value, not only calling a function.
await perception(100, ()=>console.log('done')) will work however, because of the .then method.
A subscription to perception(..) is of course cancelable, so the latter part can remain un-invoked. This benefit is only available with .subscribe(), not with await.
Where Is it Most Useful
Mock behavior - in Storybook, tests, etc. If you have a system that depends on async values, you can swap in an perception-returning function for either a Promise-returning or Observable-returning function.
For example, in this example of a batch-lookup script, you can approximate the timing with an perception, and in tests, then switch to a real endpoint, and the timing and sequencing will work the same, guaranteed.
const mockLookup = id => perception(1000, ()=>({id, username: 'foo'}));
const realLookup = id => fetch(`/someurl?id=${id}`).then(r=>r.json())
const idsToProcess = [1,2,3...];
const process = from(idsToProcess).pipe(
concatMap(mockLookup),
// concatMap(realLookup),
tap(console.log)
);
const execution = process.subscribe({complete(){ console.log('done') });
// execution.unsubscribe() // if you need to cancel3rd argument - Observer
You can pass an Observer as the 3rd argument. This is most useful for detecting when the perception is canceled.
perception(
Promise.resolve(),
() => console.log("complete"),
{ unsubscribe(){ console.log("canceled")} }
)
.subscribe()
.unsubscribe();
// "canceled"