Trong TypeScript, một promise có kiểu Promise<T>, trong đó T là kiểu của giá trị mà nó resolve. Hàm trả về promise khai báo điều đó trong kiểu trả về, và giá trị bạn nhận từ then hoặc await có kiểu T.
Cơ chế của promise (các trạng thái, hàng đợi microtask, nối chuỗi) là JavaScript thuần, được trình bày ở promise trong JavaScript. Trang này tập trung vào kiểu.
Khai báo kiểu cho new Promise và resolve
Khi new Promise là giá trị trả về của một hàm có khai báo kiểu trả về, T được lấy từ đó. Nếu đứng riêng, TypeScript không suy ra T từ các lời gọi resolve, và bạn nhận Promise<unknown>. Hãy truyền đối số kiểu một cách tường minh:
Có hai chi tiết. resolve có kiểu dựa trên T, nên resolve("42") trong một Promise<number> là lỗi biên dịch. Và resolve() không có đối số chỉ được phép khi T có chứa void: trong một Promise<number> nó là lỗi TS2794 (Expected 1 arguments, but got 0. Did you forget to include 'void' in your type argument to 'Promise'?).
Phía reject thì không có kiểu. Không có Promise<T, E>: một promise có thể reject với bất kỳ giá trị nào.
then, catch và finally
Mỗi then trả về một promise mới có kiểu theo giá trị mà callback của nó trả về. Nếu callback trả về một promise, TypeScript sẽ mở nó ra, nên bạn không bao giờ nhận Promise<Promise<T>>.
Tham số của callback catch có kiểu any trong thư viện chuẩn, không phải unknown. Không có gì ngăn bạn viết e.message trên một giá trị có thể là chuỗi. Hãy chú thích nó là unknown như ở trên, và thu hẹp kiểu trước khi dùng. try/catch bao quanh await thì tốt hơn: khi bật strict, biến catch của nó đã là unknown (xem xử lý lỗi).
Promise.all trả về một tuple
Với một mảng literal gồm các promise khác kiểu, Promise.all trả về promise của một tuple, mỗi vị trí giữ kiểu riêng của nó:
Có thể trộn các giá trị không phải promise vào, và chúng đi qua mà không thay đổi. Promise.all reject ngay khi một đầu vào bị reject, và các kết quả khác bị mất. Promise.race resolve hoặc reject theo promise hoàn tất đầu tiên và có kiểu là union của các đầu vào; Promise.any resolve với lần thành công đầu tiên và chỉ reject với một AggregateError khi mọi đầu vào đều thất bại.
Promise.allSettled và kiểu kết quả của nó
Promise.allSettled chờ mọi đầu vào và không bao giờ reject. Mỗi kết quả là một PromiseSettledResult<T>, một union mà bạn thu hẹp bằng trường status:
r.value chỉ tồn tại sau khi kiểm tra status, vì biến thể bị reject không có value. reason có kiểu any, cùng lý do như trong catch. Lệnh filter dùng một type predicate để mảng sau khi lọc có kiểu là các kết quả fulfilled.
Bọc API dùng callback thành promise
Các API cũ trả kết quả qua callback, thường theo kiểu Node (err, result) => void. Hãy bọc chúng một lần trong một hàm trả về promise có kiểu, rồi phần code còn lại có thể dùng await:
Trong Node, util.promisify làm việc này cho các hàm tuân theo quy ước (err, result), và nhiều module có sẵn đã có phiên bản promise (node:fs/promises, node:timers/promises).
Những lỗi thường gặp
- Trả về
Ttrong khi chữ ký ghiPromise<T>, hoặc ngược lại. Một hàm không phải async khai báoPromise<User>phải trả về promise; hàmasynctự động trả về promise. - Quên xử lý promise. Một lời gọi như
save(user);không cóawait,thenhaycatchvẫn biên dịch bình thường, và một lần reject sẽ trở thành unhandled rejection (theo mặc định sẽ làm tiến trình Node dừng lại). Quy tắcno-floating-promisescủa typescript-eslint bắt được các trường hợp này. - Tin vào kiểu của
.catch((e) => ...).ecủa nó có kiểuany. Hãy chú thích nó làunknown. - Dùng
new Promisebao quanh thứ đã trả về promise. Chỉ cần trả về hoặcawaitpromise có sẵn.
Câu hỏi thường gặp
Promise<T> trong TypeScript là gì?
Promise<T> là kiểu của một promise được resolve với giá trị kiểu T. Một hàm trả về Promise<string> sẽ trả lại một promise mà callback then của nó, hoặc await, cho bạn một string. Promise được resolve mà không có giá trị là Promise<void>.
Khai báo kiểu cho new Promise trong TypeScript như thế nào?
Truyền đối số kiểu: new Promise<number>((resolve, reject) => ...). Nếu không có nó, TypeScript không suy ra được giá trị từ các lời gọi resolve và kết quả là Promise<unknown>. Với promise không resolve giá trị nào, dùng new Promise<void>(resolve => ...) để được phép gọi resolve() không có đối số.
Lỗi trong catch của promise có kiểu gì?
any. Tham số reason của callback catch có kiểu any trong thư viện chuẩn, vì bất cứ thứ gì cũng có thể bị ném ra hoặc reject. Hãy tự chú thích nó là unknown (.catch((e: unknown) => ...)) và thu hẹp kiểu bằng instanceof Error trước khi dùng.
Promise.all hoạt động với kiểu thế nào trong TypeScript?
Promise.all trên một mảng literal trả về kiểu tuple với một phần tử cho mỗi đầu vào, theo đúng thứ tự: Promise.all([getUser(), getCount()]) là Promise<[User, number]>, nên destructuring cho mỗi giá trị kiểu riêng của nó. Trên một mảng cùng kiểu, T[], nó trả về Promise<T[]>.
Promise.all và Promise.allSettled khác nhau thế nào?
Promise.all reject ngay khi có một đầu vào bị reject. Promise.allSettled luôn resolve, với một mảng các object PromiseSettledResult<T>: { status: "fulfilled", value } hoặc { status: "rejected", reason }. Kiểm tra status để thu hẹp kiểu của từng phần tử.