在 TypeScript 中,Promise 的类型是 Promise<T>,其中 T 是它兑现时的值的类型。返回 Promise 的函数在返回类型中声明它,而你通过 then 或 await 得到的值类型就是 T。
Promise 的运行机制(状态、微任务队列、链式调用)属于普通 JavaScript,在 JavaScript Promise 中讲解。本页只讲类型。
为 new Promise 和 resolve 标注类型
当 new Promise 作为一个声明了返回类型的函数的返回值时,T 就来自那个返回类型。单独使用时,TypeScript 不会从 resolve 调用中推断 T,你得到的是 Promise<unknown>。显式传入类型参数:
有两个细节。resolve 的类型来自 T,所以在 Promise<number> 中写 resolve("42") 会编译出错。而不带参数的 resolve() 只有在 T 包含 void 时才允许:在 Promise<number> 中它会报错 TS2794(Expected 1 arguments, but got 0. Did you forget to include 'void' in your type argument to 'Promise'?)。
reject 这一边没有类型。并不存在 Promise<T, E>:Promise 可以用任何值拒绝。
then、catch 与 finally
每个 then 都返回一个新的 Promise,其类型由回调的返回值决定。如果回调返回的是 Promise,TypeScript 会把它解开,所以你永远不会得到 Promise<Promise<T>>。
在标准库中,catch 回调的参数类型是 any,而不是 unknown。没有什么能阻止你在一个可能是字符串的值上写 e.message。像上面那样把它标注为 unknown,使用前先收窄。在 await 外面套 try/catch 会更好:在 strict 下,它的 catch 变量已经是 unknown(参见错误处理)。
Promise.all 返回元组
传入由不同 Promise 类型组成的数组字面量时,Promise.all 返回一个元组的 Promise,每个位置都保留自己的类型:
可以混入非 Promise 的值,它们会原样传递。只要有一个输入被拒绝,Promise.all 就会立即拒绝,其他结果都会丢失。Promise.race 以第一个完成的结果兑现或拒绝,类型是各输入的联合类型;Promise.any 以第一个成功的结果兑现,只有在所有输入都失败时才以 AggregateError 拒绝。
Promise.allSettled 及其结果类型
Promise.allSettled 会等待每一个输入,并且永远不会拒绝。每个结果都是 PromiseSettledResult<T>,这是一个联合类型,可以用 status 字段来收窄:
只有在检查 status 之后才能访问 r.value,因为拒绝的那个变体没有 value。reason 是 any,原因和 catch 中一样。filter 使用了类型谓词,这样过滤后的数组类型就是已兑现的结果。
把回调式 API 包装成 Promise
较老的 API 通过回调报告结果,通常是 Node 风格的 (err, result) => void。把它们包装一次,变成返回带类型 Promise 的函数,其余代码就可以使用 await 了:
在 Node 中,util.promisify 可以对遵循 (err, result) 约定的函数完成这件事,而且很多内置模块已经有了 Promise 版本(node:fs/promises、node:timers/promises)。
常见错误
- 签名写的是
Promise<T>却返回了T,或者反过来。 一个声明为Promise<User>的非 async 函数必须返回 Promise;async函数会自动返回 Promise。 - 忘记处理 Promise。 像
save(user);这样没有await、then或catch的调用可以正常编译,而一旦被拒绝,就会变成未处理的拒绝(默认会让 Node 进程退出)。typescript-eslint 的no-floating-promises规则可以捕获这类问题。 - 相信
.catch((e) => ...)的类型。 它的e是any。把它标注为unknown。 - 给已经返回 Promise 的东西再套一层
new Promise。 直接返回或await现有的 Promise 就行。
常见问题
TypeScript 中的 Promise<T> 是什么?
Promise<T> 是一个以 T 类型的值兑现的 Promise 的类型。返回 Promise<string> 的函数交给你一个 Promise,它的 then 回调或 await 得到的是 string。不带值兑现的 Promise 是 Promise<void>。
TypeScript 中如何为 new Promise 标注类型?
传入类型参数:new Promise<number>((resolve, reject) => ...)。不传的话,TypeScript 无法从 resolve 调用中推断出值的类型,结果是 Promise<unknown>。如果 Promise 不带任何值兑现,就用 new Promise<void>(resolve => ...),这样才允许不带参数调用 resolve()。
Promise 的 catch 中错误是什么类型?
any。在标准库中,catch 回调的 reason 参数类型是 any,因为任何东西都可能被抛出或拒绝。自己把它标注为 unknown(.catch((e: unknown) => ...)),使用前再用 instanceof Error 收窄。
TypeScript 中 Promise.all 的类型是怎样的?
对数组字面量调用 Promise.all,返回的是元组类型,每个输入对应一个元素,顺序不变:Promise.all([getUser(), getCount()]) 是 Promise<[User, number]>,所以解构后每个值都有自己的类型。对同一类型的数组 T[] 调用,返回 Promise<T[]>。
Promise.all 和 Promise.allSettled 有什么区别?
只要有一个输入被拒绝,Promise.all 就会立即拒绝。Promise.allSettled 总是会兑现,结果是 PromiseSettledResult<T> 对象组成的数组:{ status: "fulfilled", value } 或 { status: "rejected", reason }。检查 status 就能收窄每一个结果。