271

I tried to define a type of async function, but failed in compilation, see below:

interface SearchFn {
    async (subString: string): string;
}

class A {
    private Fn: SearchFn
    public async do():Promise<string> {
        await this.Fn("fds") // complain here: cannot invoke an expression whose type lacks a call signature
        return ''
    }
}

Can anyone help me work this out?

2
  • Promise<boolean> doesn't work? Commented Aug 3, 2016 at 12:53
  • Please show how/where you are defining Fn. Commented Aug 3, 2016 at 13:46

4 Answers 4

425

It works if you just declare the return type of the function to be a Promise:

interface SearchFn {
    (subString: string): Promise<boolean>;
}

or as a type declaration:

type SearchFn = (subString: string) => Promise<boolean>;

Microsoft's TS Linter will recommend this second syntax.

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

5 Comments

Smart, thank you, async func actually is a Promise returned function.
@Ron worth mentioning thats not correct, async function simply uses await. See Motti's answer
People should note that a function that returns a promise is not necessarily an async function.
what if there's no returned value? ): Promise<undefined> is failing for me. I just need an async function so I can use await inside it...
@dcsan Promise<void>
116

The async keyword is used to indicate to the compiler/runtime that the function in question will use await internally (so it can put in the required scaffolding to enable it).

This means that async only has meaning for the implementation of the function, not it's interface. Therefore having async on an interface's method isn't useful, you want to say that the function returns a certain Promise (in your case Promise<string>) but you don't want to enforce that the interface's implementer implements this in a certain way (using await).

So as other said before me:

interface SearchFn {
    (subString: string): Promise<string>;
}

Then, whoever chooses to implement this function can choose to use async, plain old Promise.then or perhaps even some new methodology that will come up in the future.

3 Comments

Not quite. Functions marked async also return promises. So async function x() { return true; } returns a type of Promise<boolean>. So personally, I think the Typescript team messed up here and should have allowed using async in templates to indicate that methods return promises. But nobody asked me. ;)
Actually there is an interface-level difference, with exceptions. When calling a function that returns a Promise, the function may throw an exception before returning the Promise, which will result in a first-level exception. Whereas an async function, when called, will never throw an exception : at worst, one will occur during the resolution of the promise and the promise will be rejected, but calling the function per se will always be safe.
@Gouvernathor that's a very good point. Sounds like an oversight in TS's design.
18

Pass the type of the returned object to the Promise generic.

type SearchFn = (subString: string): Promise<string>;

Alternatively you can declare an AsyncFunction generic type.

type AsyncFunction <A,O> = (...args:A) => Promise<O> 
type SearchFn = AsyncFunction<[string], string>

AsyncFunction is a type generic that receives two type variables - the type of the input(A), and the type of the output.

Comments

15

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.

Comments

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.