That's a tricky topic. A function marked with async will return a Promise. But returning a Promise and using async are not equivalent. I will explain why.
For example, in your case you can define your interface like this:
interface Foo {
(source: string): Promise<string>;
}
Then you have different options to write a function that complies with the interface:
// complies
const foo1: Foo = (input: string) => {
return Promise.resolve(input);
};
// complies
const foo2: Foo = async (input: string) => {
return Promise.resolve(input);
};
// complies: the return value gets wrapped in a Promise
const foo3: Foo = async (input: string) => {
return input;
};
Now, one of the magical behaviors of the async keyword, is that the returned value will get wrapped in a Promise. For example:
// valid
async function bar1(): Promise<number> {
return 5;
}
Also, it's worth noting that promises get automatically flattened (but I don't know how that works internally), so the following code is valid:
// valid
async function bar2(): Promise<Promise<number>> {
return Promise.resolve(5);
}
// also valid... weird
async function bar3(): Promise<Promise<number>> {
return 5;
}
So far, there is no meaningful difference between using async and Promise, so one could argue that async is not a keyword that belongs in interfaces, because you can express the contract in terms of promises. It's just a syntax sugar so we can use the await keyword. An implementation detail, right?
Sadly. Is is not. There is one additional effect of the async keyword that does affect the contract and has to do with the nature of promises.
Look at the following function:
// supposedly compliant
function nonAsyncFunction(input: number): Promise<void> {
if (input < 0) {
throw new TypeError('"input" should be positive');
}
return Promise.resolve();
}
Harmless, right? If you handle the error using try/catch (using await in the function call), there is no problem. But if you were to do handle the error using Promise.prototype.catch:
// The function throws before returning a promise, so it's not part of the promise chain
nonAsyncFunction(-1)
.catch((err) => {
console.log('caught!');
});
The error will not be caught, because the function throws and an exception is raised instead of returning a Promise.
On the other hand, a function like the following will return a rejected promise and you can catch it either with try/catch or Promise.prototype.catch:
async function asyncFunction(input: number): Promise<void> {
if (input < 0) {
throw new TypeError('"input" should be positive');
}
return;
}
The root of the problem is that we have two ways of reporting errors, one by "rejecting" promises and other by raising errors. We have no way of enforcing a function that returns a Promise that it reports errors using only promises as well.
In conclusion, async is not equivalent to defining the return type as Promise. In practice, using async ensures any error raised will be returned as a rejected promise, which is a valuable thing to enforce. Sadly, it is not possible to define an async function in an interface.
The correct answer to your question would be that it can't be done, strictly speaking.
Fn.