The short answer
Write the promise: @returns {Promise<Order>}. Inside the function, return a plain Order; outside, await the call to get one:
import { readFile } from "node:fs/promises";
/**
* @typedef {object} Order
* @property {string} id
* @property {number} total
*/
/**
* @param {string} id
* @returns {Promise<Order>}
*/
export async function loadOrder(id) {
const text = await readFile(new URL(`./orders/${id}.json`, import.meta.url), "utf8");
/** @type {Order} */
const order = JSON.parse(text);
return order;
}
const order = await loadOrder("A-1001");
console.log(order.total.toFixed(2));import { readFile } from "node:fs/promises";
export interface Order {
id: string;
total: number;
}
export async function loadOrder(id: string): Promise<Order> {
const text = await readFile(new URL(`./orders/${id}.json`, import.meta.url), "utf8");
const order: Order = JSON.parse(text);
return order;
}
const order = await loadOrder("A-1001");
console.log(order.total.toFixed(2));jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": ["node"]
},
"include": ["*.js"]
} The @type {Order} on the parsed value matters too: JSON.parse returns any, and without the annotation the function would return whatever the file holds, unchecked.
Why it works
An async function always returns a promise, even when its body returns a plain object, so its declared return type has to be a promise of something. tsc checks each return against the type inside the promise (Order here), and await unwraps it again for the caller.
The same thing in TypeScript
The TypeScript equivalent tab declares async function loadOrder(id: string): Promise<Order>. The annotation is the same type; only its position changes.
What goes wrong
Writing the inner type alone is an error, TS1064, and its message suggests the fix:
/** @typedef {{ id: string, total: number }} Order */
/** @param {string} id @returns {Order} */error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<Order>'?
export async function loadOrder(id) {
return { id, total: 40 };
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": ["node"]
},
"include": ["*.js"]
} The common runtime bug shows up as a type error too. Without await, order is the promise, not the order, so order.total does not exist (TS2339):
/** @typedef {{ id: string, total: number }} Order */
/**
* @param {string} id
* @returns {Promise<Order>}
*/
export async function loadOrder(id) {
return { id, total: 40 };
}
const order = loadOrder("A-1001");
console.log(order.total);error TS2339: Property 'total' does not exist on type 'Promise<Order>'.jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": ["node"]
},
"include": ["*.js"]
} Gotchas
returnof a promise is fine. Returning another promise ofOrderfrom an async function also satisfiesPromise<Order>; promises do not nest.- Top-level
awaitneeds a module. The last two lines of the first example run at the top level, which works in an ES module undermodule: "nodenext"(see TS1378 when it does not). - Floating promises are not reported. tsc does not complain when you call an async function and ignore the promise. That needs a lint rule, such as
no-floating-promisesin typescript-eslint.
Questions
Do I need @returns on an async function at all?
No. Without it, tsc infers Promise<…> of whatever the function returns. Write it when the function is exported or when the inferred type is too loose, for example when the value comes from JSON.parse, which returns any.
Does the @async tag do anything?
No. TypeScript does not read @async; the async keyword is what makes the function return a promise. The @async page shows the tag being ignored.
What type does an async function that returns nothing have?
Promise<void>. Write @returns {Promise<void>}, or leave the tag out and let tsc infer it.
Sources
- JSDoc Reference (@returns) · TypeScript handbook
- async function · MDN Web Docs
- fs.promises.readFile · Node.js documentation
Last reviewed 21 October 2026.