Promise<T> is the type of a promise that resolves to a T. An async function always returns one, so its JSDoc says @returns {Promise<T>} and its return statements return plain T values. await unwraps the type again: await loadProducts() is Product[].
Because the promise and its value have different types, a forgotten await is caught: reading .length from a Promise<string[]> is TS2339, as the second example shows.
At a glance
| Use | JSDoc in a .js file | TypeScript in a .ts file |
|---|---|---|
| An async function | /** @returns {Promise<Product[]>} */
async function load() {} | async function load(): Promise<Product[]> {} |
| A promise parameter | /** @param {Promise<Response>} pending */ | (pending: Promise<Response>) |
| A promise of nothing | /** @returns {Promise<void>} */ | (): Promise<void> |
Examples
/** @typedef {{ sku: string, name: string, price: number }} Product */
/**
* @param {string} category
* @returns {Promise<Product[]>}
*/
export async function loadProducts(category) {
const response = await fetch(`/api/products?category=${encodeURIComponent(category)}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return /** @type {Product[]} */ (await response.json());
}
loadProducts("shirts").then((products) => products.map((p) => p.name));type Product = { sku: string; name: string; price: number };
export async function loadProducts(category: string): Promise<Product[]> {
const response = await fetch(`/api/products?category=${encodeURIComponent(category)}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return (await response.json()) as Product[];
}
loadProducts("shirts").then((products) => products.map((p) => p.name));jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @returns {Promise<string[]>} */
async function loadSkus() {
return ["TEE-01", "MUG-07"];
}
export async function countSkus() {
const skus = loadSkus();
return skus.length;error TS2339: Property 'length' does not exist on type 'Promise<string[]>'.
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /**
* @param {number} ms
* @returns {Promise<void>}
*/
export function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* @template T
* @param {Promise<T>} work
* @param {number} ms
* @returns {Promise<T>}
*/
export function withTimeout(work, ms) {
return Promise.race([work, delay(ms).then(() => Promise.reject(new Error("Timed out")))]);
}
withTimeout(fetch("/api/cart"), 5000).then((response) => response.status);jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Typing JSON from fetch
response.json() returns Promise<any>, so its result is unchecked. The first example casts it to the expected type with /** @type {Product[]} */ (await response.json()). The cast is a claim, not a check; for data you do not control, cast to unknown and validate instead.
new Promise
A function that builds a promise with new Promise declares the resolved type in @returns, as delay() does with Promise<void>; TypeScript then checks what resolve is called with. Generic helpers such as withTimeout take @template T so the result keeps the input's type.
Gotchas
- An
asyncfunction's return type must be written asPromise<T>;@returns {T}alone is TS1064. Promise.all([a, b])is typed as a tuple of the resolved types, so destructuring its result keeps each type.
Questions
How do I document the return type of an async function in JSDoc?
@returns {Promise<T>}, where T is the resolved value. For an async function that resolves to nothing, @returns {Promise<void>}.
Sources
- Everyday Types: functions which return promises · TypeScript handbook
- Using promises · MDN
Last reviewed 20 October 2026.