1

In an effort to reduce the cognitive load on my aging gray matter, I'm providing custom overloads for specific events in an event emitter.

// default signature
function on(event: string | symbol, listener: (...args: any[]) => void): void;
// custom signatures
function on(event: "error", listener: (error: string, code: number) => void): void;
function on(event: "data", listener: (text: string) => void): void;
function on(event: "chunk", listener: (text: string) => void): void;
function on(event: "complete" | "done", listener: (id: number) => void): void;
// most general signature
function on(event: any, listener: (...args: any[]) => void) {
  // ...
}

// correctly resolve data as 'string'
on("data", (data) => {});
on("chunk", (data) => {});
// incorrectly resolves id as any from default signature
on("complete", (id)  => {});

Per the TypeScript docs, I've ordered general overloads after specific overloads.

My question is; why does the union type ("complete" | "done") not work as expected but separating them ("data" and "chunk") does?

1 Answer 1

1

From the documentation on overloads:

In order for the compiler to pick the correct typecheck, it follows a similar process to the underlying JavaScript. It looks at the overload list, and proceeding with the first overload attempts to call the function with the provided parameters. If it finds a match, it picks this overload as the correct overload. For this reason, it’s customary to order overloads from most specific to least specific.

I do see that sometimes the compiler does choose overloads "out of order" by looking for more specific signatures even when they are not the first matching signature in the list (e.g., see GitHub issue Microsoft/TypeScript#9443)... but when in doubt, you'll get the best behavior if you list your overloads from the most specific to the most general. For example:

// custom signatures
function on(event: "error", listener: (error: string, code: number) => void): void;
function on(event: "data", listener: (text: string) => void): void;
function on(event: "chunk", listener: (text: string) => void): void;
function on(event: "complete" | "done", listener: (id: number) => void): void;
// default signature
function on(event: string | symbol, listener: (...args: any[]) => void): void;
// implementation signature
function on(event: any, listener: (...args: any[]) => void) {
  // ...
}

This should work how you want. Hope that helps. Good luck!

Sign up to request clarification or add additional context in comments.

2 Comments

No idea how I kept missing that! But your answer works as advertised. Thanks
You can even combine the data and chunk call for a more thorough answer (as I had imagined it)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.