I've been working on converting some code from JavaScript to TypeScript, and I have a specific requirement for the return type of a function.
The function should return void
if a callback
parameter is provided, otherwise it should return Promise<object>
. There's also an optional parameter called settings
that can be passed before the callback
parameter. In certain cases, the callback
parameter could be passed in as the settings
parameter, which the function handles with the initial few lines of logic.
To maintain backwards compatibility and avoid redundancy in code, I want to avoid creating separate functions like savePromise
or saveCallback
. So, I'm looking for a way to make TypeScript understand this logic intelligently.
type CallbackType<T, E> = (response: T | null, error?: E) => void;
class User {
save(data: string, settings?: object, callback?: CallbackType<object, string>): Promise<object> | void {
if (typeof settings === "function") {
callback = settings;
settings = undefined;
}
if (callback) {
setTimeout(() => {
callback({"id": 1, "settings": settings});
}, 1000);
} else {
return new Promise((resolve) => {
setTimeout(() => {
resolve({"id": 1, "settings": settings});
}, 1000);
});
}
}
}
const a = new User().save("Hello World"); // Expected type: Promise<object>, eventually resolves to {"id": 1, "settings": undefined}
const b = new User().save("Hello World", (obj) => {
console.log(obj); // {"id": 1, "settings": undefined}
}); // Expected type: void
const c = new User().save("Hello World", {"log": true}); // Expected type: Promise<object>, eventually resolves to {"id": 1, "settings": {"log": true}}
const d = new User().save("Hello World", {"log": true}, (obj) => {
console.log(obj); // {"id": 1, "settings": {"log": true}}
}); // Expected type: void
In my ideal scenario, the types for the function should look something like this:
save(data: string, settings?: object): Promise<object>;
save(data: string, callback: CallbackType<object, string>): void;
save(data: string, settings: object, callback: CallbackType<object, string>): void;
I've attempted to handle the case where the callback
parameter is passed in as the settings
parameter by defining the function signature as follows:
save(data: string, settings?: object | CallbackType<object, string>, callback?: CallbackType<object, string>): Promise<object> | void
However, this approach feels messy and it seems that TypeScript doesn't infer that settings
will always be an optional object after the initial code block in the function. This leads to the need for explicit type casting when calling callback
, which isn't ideal.
My question is, how can I achieve this behavior in TypeScript more elegantly?