It is possible, you just have to loosen the implementation signature (which callers don't see, they only see the overload signatures):
function pop<T>(items: T[], cb: (item: T) => void, count?: 1): void;
function pop<T>(items: T[], cb: (items: T[]) => void, count: number): void;
function pop<T>(
items: T[],
cb: (items: T | T[]) => void,
count = 1,
): void {
if (count === 1) {
cb(items[0]);
} else if (count > 1) {
cb(items.slice(0, count));
} else {
cb([]);
}
}
Playground link
(Since the default for count
was 1
, I've folded it into the first overload signature by making count
optional when its type is 1
.)
Beware though that TypeScript will assume any non-literal (or at least not immediately-obviously-constant) number
you pass as count
means that the callback expects an array, since the type is number
, not 1
. So this overload only works to provide the callback with the item (rather than an array) if you specify 1
literally (or as an immediately-obviously-constant) in the call.
Examples:
declare let items: number[];
pop(
items,
(item) => {
console.log(item); // type is `number`
},
1
);
let count = 1;
pop(
items,
(item) => {
console.log(item); // type is `number[]`, not `number`
},
count
);
Playground link
Even not the type of item
is number[]
, at runtime it will receive a single number
, not an array, because the runtime code just knows that the count
parameter is 1
, not why it's 1
. As Jörg W Mittag points out in the comments, this is because the overloads are purely a compile-time / type-checking thing in TypeScript; the only part that actually happens at runtime is the JavaScript implementation, which doesn't know about the static types. (This is in contrast to languages like Java where the overloads are literally separate functions and the specific one being called is determined at compile-time, not runtime.)
You can fix that in a couple of ways:
- Define two separate methods instead,
popOne
and pop
/popSome
or similar.
- Require that
const
only be a literal or compile-time constant.
#1 is self-explanatory, but captain-yossarian shows us how to do #2, via an OnlyLiteral
generic type:
type OnlyLiteral<N> = N extends number ? number extends N ? never : N : never;
Then the second overload signature is:
function pop<T>(items: T[], cb: (items: T[]) => void, count: OnlyLiteral<number>): void;
...and the case giving us number[]
in my earlier example becomes a compile-time error: playground link