TypeScript 中的异步编程,是前端与 Node.js 开发中极其重要的一部分。无论是发送网络请求、读取文件、执行定时任务,还是数据库访问,都离不开“异步”机制。下面我将使用 DokuWiki 语法,系统讲解 TypeScript 中与异步相关的核心知识点。
在理解 TypeScript 异步之前,首先要明白“同步”和“异步”的区别。
同步代码会按照从上到下的顺序依次执行,前面的任务没有完成,后面的任务就不能开始。
console.log("任务1");
console.log("任务2");
console.log("任务3");
执行结果:
任务1 任务2 任务3
这种执行方式简单直观,但如果某个任务耗时很长,例如网络请求或者文件读取,那么后续任务就会被阻塞。
异步代码的特点是:某个耗时任务启动后,不必等待它完成,程序可以继续执行后面的代码,等任务完成后再处理结果。
console.log("开始");
setTimeout(() => {
console.log("异步任务完成");
}, 1000);
console.log("结束");
执行顺序通常为:
开始 结束 异步任务完成
这说明 `setTimeout` 中的逻辑并不会阻塞后续代码执行。
TypeScript 是 JavaScript 的超集,因此它的异步模型本质上继承自 JavaScript。之所以需要异步,主要有以下原因:
例如,浏览器中如果所有请求都采用同步方式,那么页面会卡住,用户体验会非常差。
虽然题目是 TypeScript,但 TypeScript 的异步建立在 JavaScript 运行机制之上,所以必须了解事件循环(Event Loop)。
调用栈负责执行当前同步代码。函数调用时入栈,执行完成后出栈。
像 `setTimeout`、HTTP 请求、文件系统操作等,不是直接由 JavaScript 引擎完成,而是交给宿主环境处理。
异步任务完成后,对应的回调函数会进入任务队列,等待调用栈空闲后执行。
事件循环不断检查调用栈是否为空,如果为空,就从任务队列中取出任务执行。
可以简单理解为:
在 Promise 出现之前,JavaScript 主要通过回调函数处理异步逻辑。
function fetchData(callback: (data: string) => void): void {
setTimeout(() => {
callback("获取到的数据");
}, 1000);
}
fetchData((data) => {
console.log(data);
});
这里 `callback` 是一个函数参数,异步任务完成后被调用。
当多个异步任务相互依赖时,就容易出现“回调地狱”。
setTimeout(() => {
console.log("任务1完成");
setTimeout(() => {
console.log("任务2完成");
setTimeout(() => {
console.log("任务3完成");
}, 1000);
}, 1000);
}, 1000);
这种嵌套层级很深,代码难以维护、难以扩展、错误处理也不统一。
为了解决回调函数的问题,ES6 引入了 `Promise`。TypeScript 对 Promise 提供了良好的类型支持。
`Promise` 可以理解为“一个未来才会完成的结果”。
Promise 有三种状态:
状态一旦从 `pending` 变为成功或失败,就不能再改变。
const p = new Promise<string>((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
resolve("操作成功");
} else {
reject("操作失败");
}
}, 1000);
});
这里的 `Promise<string>` 表示最终成功时返回的是字符串类型。
p.then((result) => {
console.log("成功:", result);
}).catch((error) => {
console.error("失败:", error);
});
Promise 最大的优势之一就是链式调用。
function step1(): Promise<number> {
return Promise.resolve(1);
}
function step2(num: number): Promise<number> {
return Promise.resolve(num + 1);
}
function step3(num: number): Promise<number> {
return Promise.resolve(num + 1);
}
step1()
.then((res) => step2(res))
.then((res) => step3(res))
.then((res) => {
console.log("最终结果:", res);
})
.catch((err) => {
console.error(err);
});
这种方式比多层回调更加清晰。
TypeScript 相比 JavaScript 的优势,在于可以明确异步返回值类型。
function getUserName(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Alice");
}, 1000);
});
}
这里函数返回的不是 `string`,而是 `Promise<string>`。这表示“未来会得到一个字符串”。
type User = {
id: number;
name: string;
};
function getUser(): Promise<User> {
return Promise.resolve({
id: 1,
name: "Tom"
});
}
如果异步函数没有明确返回值,可使用 `Promise<void>`。
function logAsync(): Promise<void> {
return new Promise((resolve) => {
setTimeout(() => {
console.log("记录完成");
resolve();
}, 500);
});
}
某些函数始终抛出错误或永远不会正常完成,可以涉及 `Promise<never>`,不过实际业务中使用较少。
`async / await` 是建立在 Promise 之上的语法糖,让异步代码看起来更像同步代码。
只要在函数前加上 `async`,该函数就会返回一个 Promise。
async function hello(): Promise<string> {
return "Hello TypeScript";
}
虽然直接返回的是字符串,但实际上会被包装成 `Promise<string>`。
`await` 用于等待 Promise 完成,并获取其结果。
function getData(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve("数据加载完成");
}, 1000);
});
}
async function main(): Promise<void> {
const data = await getData();
console.log(data);
}
main();
使用 `try…catch` 捕获异步错误是 async/await 的常见方式。
async function requestData(): Promise<void> {
try {
const result = await Promise.reject("请求失败");
console.log(result);
} catch (error) {
console.error("捕获到错误:", error);
}
}
很多初学者容易误以为 `async/await` 是另一套独立机制,实际上不是。
它们的关系可以总结为:
也就是说,学习 `async/await` 前,必须先掌握 Promise。
快速创建一个成功状态的 Promise。
const p = Promise.resolve(123);
快速创建一个失败状态的 Promise。
const p = Promise.reject("出错了");
用于并发执行多个异步任务,全部成功才算成功。
async function demo(): Promise<void> {
const results = await Promise.all([
Promise.resolve("A"),
Promise.resolve("B"),
Promise.resolve("C")
]);
console.log(results);
}
返回结果是数组:
["A", "B", "C"]
如果其中一个失败,则整体失败。
无论成功还是失败,都会等待全部任务完成。
async function demo(): Promise<void> {
const results = await Promise.allSettled([
Promise.resolve("成功1"),
Promise.reject("失败1"),
Promise.resolve("成功2")
]);
console.log(results);
}
适合需要统计全部执行结果的场景。
谁先完成,就返回谁的结果。
async function demo(): Promise<void> {
const result = await Promise.race([
new Promise((resolve) => setTimeout(() => resolve("快任务"), 500)),
new Promise((resolve) => setTimeout(() => resolve("慢任务"), 1000))
]);
console.log(result);
}
返回第一个成功的 Promise,如果全失败才报错。
这在需要“多个备用接口取最快成功结果”时非常有用。
异步并不等于并发。理解串行与并发很重要。
前一个完成后再执行下一个。
async function serial(): Promise<void> {
const a = await Promise.resolve("A");
const b = await Promise.resolve("B");
const c = await Promise.resolve("C");
console.log(a, b, c);
}
多个任务同时开始,再统一等待结果。
async function parallel(): Promise<void> {
const p1 = Promise.resolve("A");
const p2 = Promise.resolve("B");
const p3 = Promise.resolve("C");
const results = await Promise.all([p1, p2, p3]);
console.log(results);
}
在彼此无依赖的情况下,并发通常效率更高。
async function badExample() {
Promise.reject("错误");
}
这样可能导致未处理的 Promise 异常。
async function goodExample(): Promise<void> {
try {
await Promise.reject("错误");
} catch (e) {
console.error(e);
}
}
在 TypeScript 中,`catch` 捕获的错误通常建议先做类型收窄。
try {
throw new Error("出错");
} catch (e) {
if (e instanceof Error) {
console.log(e.message);
}
}
TypeScript 中可以为异步函数、回调函数、接口方法声明明确类型。
type AsyncFunc = (id: number) => Promise<string>;
const fetchName: AsyncFunc = async (id) => {
return `name-${id}`;
};
interface UserService {
getUser(id: number): Promise<{ id: number; name: string }>;
}
async function wrap<T>(value: T): Promise<T> {
return value;
}
泛型让异步函数更具复用性。
type Post = {
id: number;
title: string;
};
async function fetchPosts(): Promise<Post[]> {
const response = await fetch("/api/posts");
const data = await response.json();
return data as Post[];
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function demo(): Promise<void> {
console.log("开始");
await sleep(1000);
console.log("1秒后执行");
}
async function retry(fn: () => Promise<string>, count: number): Promise<string> {
for (let i = 0; i < count; i++) {
try {
return await fn();
} catch (e) {
if (i === count - 1) throw e;
}
}
throw new Error("最终失败");
}
实际上仍然是 Promise。
实际上它只是暂停当前 async 函数内部的执行。
如果写成串行反而可能更慢。
实际上 Promise 内部很多时候仍然基于回调触发结果。
TypeScript 的异步编程,核心可以归纳为以下几个层次:
如果把异步知识体系看作一个成长路径,那么可以概括为:
在实际开发中,建议优先使用 `async/await` 编写业务逻辑,同时理解 Promise 的底层思想;在需要并发时配合 `Promise.all`、`Promise.allSettled` 等 API 使用。只有把“运行机制 + Promise + async/await + 类型约束”四者结合起来,才能真正掌握 TypeScript 异步编程。
如果你愿意,我还可以继续为你输出一份:
你只要回复一句:继续扩展 即可。