#partially async chain of responsibility

10 messages · Page 1 of 1 (latest)

lost flume
#

Is there a way to create a CoR (Chain of Responsibility) that "knows"/"infers" when when called ("handle") results in promise?
For example, I want to create a simple chain with handlers.
input for each handler is number and output is string.
Handler can be sync or async (return Promise<string>) .

I want to use the chain like:

const syncHandlerInstance = new HandlerA()
const asyncHandlerInstance = new HandlerB()

const chain = syncHandlerInstance.setNext(asyncHandlerInstance)
const res = chain.handle(5)

I want typescript to know/infer that "res" is Promise<string> (as SOME (at least one) of the handlers in the chain is async)

pallid rampart
#

What's the reason that you must know whether the chain contains an async handler or not?

#

FYI, await is no op when used on a non promise, so you can simply do:

const fnSync = () => 42
const syncResult = await fnSync()

const fnAsync = async () => 42
const asyncResult = await fnAsync()
lost flume
#

I just have lots of handler and I’m creating different chains that sometimes include only sync handlers and operate in sync context..

#

So I would like to not call it as async when not needed…

pallid rampart
#

The easiest solution I can think of is to do something like:

new SyncChain()
    .chain(syncHandlerA)
    .chain(syncHandlerB)
    .chain(asyncHandlerC) // type error

And a corresponding AsyncChain where it accepts both sync and async handlers.

#

If you want to do an API like new Chain() and you can chain both sync and async, and it knows whether any async chain has been added, that's also possible, but I think if your goal is to "ensure this chain only has sync" then that's kind of unnecessary.

lost flume
#

My ultimate goal was to create this one chain API “aware” of it being (partially or fully) async… I thought I could use some generics and inference

pallid rampart
#

You absolutely can.

opaque heronBOT
#
type Handler = (() => unknown) | (() => Promise<unknown>)

type Chain<T extends true = never> = {
    add: <H extends Handler>(
        handler: H,
    ) => Chain<T | (H extends () => Promise<unknown> ? true : never)>
    execute: () => true extends T ? Promise<void> : void
}

declare const chain: Chain

const syncOnly = chain.add(() => 42).add(() => 69).execute()
//    ^? - const syncOnly: void

const hasAsync = chain.add(() => 42).add(async () => 69).execute()
//    ^? - const hasAsync: Promise<void>