Recipe · Functions

How do I type what an async function returns in JSDoc?

  • Node.js (ES modules)
  • @returns
  • Promise
  • async
  • Checked with TypeScript 5.9

The problem My async function returns an order. Do I write @returns {Order} or @returns {Promise<Order>}, and why does tsc say the property does not exist when I use the result?

The short answer

Write the promise: @returns {Promise<Order>}. Inside the function, return a plain Order; outside, await the call to get one:

orders.js orders.ts TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
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));
An async function that returns Promise<Order>.
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:

orders.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: orders.js(4,34): 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>'? 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. orders.js(4,34): 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>'? 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. orders.js(4,34): 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>'? checked 21 October 2026
/** @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 };
}
@returns without the Promise.
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):

orders.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: orders.js(13,19): error TS2339: Property 'total' does not exist on type 'Promise<Order>'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. orders.js(13,19): error TS2339: Property 'total' does not exist on type 'Promise<Order>'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. orders.js(13,19): error TS2339: Property 'total' does not exist on type 'Promise<Order>'. checked 21 October 2026
/** @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>'.
Using the promise as if it were the order.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": ["node"]
  },
  "include": ["*.js"]
}

Gotchas

  • return of a promise is fine. Returning another promise of Order from an async function also satisfies Promise<Order>; promises do not nest.
  • Top-level await needs a module. The last two lines of the first example run at the top level, which works in an ES module under module: "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-promises in 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

Last reviewed 21 October 2026.