ProgrammingFullstack Developer

How does the typing of asynchronous functions and Promises work in TypeScript? How to safely describe return values, catch typing errors, and what are the differences from regular functions? Provide examples.

Pass interviews with Hintsage AI assistant

Answer.

In TypeScript, asynchronous functions (async/await) always return a Promise. The return type is specified as Promise<Type>. When describing a function, it's important to explicitly type the result so that TypeScript correctly works with types inside the promise. Typing errors can occur if the intermediate values do not match those declared.

Asynchronous function:

async function fetchUser(id: number): Promise<User> { const response = await fetch(`/api/user/${id}`); const data: User = await response.json(); return data; }

If the function is supposed to return an error or rejection, the promise type should not directly describe the error (Promise<Error>), as the promise rejection is not typed, but caught through catch.

Typing promise chains is important:

function getNumber(): Promise<number> { return Promise.resolve(42); } getNumber().then(val => val.toFixed(2)); // TypeScript knows that val is a number

Trick question.

Question: Can an asynchronous function return something other than a promise?

Answer: No. An asynchronous function always returns a Promise object. If a non-promise is explicitly returned, TypeScript will automatically wrap the returned value in a promise.

async function test() { return 1; } const result = test(); type ResultType = typeof result; // Promise<number>

Examples of real errors due to ignorance of the subtleties of the topic.


Story

A developer wrote a function without explicitly typing the return value, assuming it returned an object of a certain type. When the logic changed, the function started returning a different type, and this was not noticed immediately, as TypeScript, based on type inference, did not issue an error. As a result, errors manifested only at runtime when the function consumer tried to access a non-existent property.


Story

In one project, promise chains were not typed. Attempting to access a non-existent method on the result led to errors. Thus, the result then(response => response.data) was considered any, and the error catching of fields or methods manifested only at runtime. The error went into production and was discovered only with the help of users.


Story

An asynchronous function was declared with the type Promise<Error>, thinking that this would allow catching errors, but in reality, errors were not thrown through throw, and the promise was rejected with a different type of value. This led to implicit mixing of errors and valid values, resulting in bugs in the logic of handling promise results.