TypeScript Promise 详解及最佳实践(七)

在现代 JavaScript 开发中,异步编程是不可或缺的一部分。TypeScript 作为 JavaScript 的超集,不仅提供了强大的类型系统,还对 Promise 进行了全面的支持。本文将详细介绍 Promise 的工作原理、常见用法以及最佳实践,帮助你在 TypeScript 项目中更好地管理和编写异步代码。

为什么需要 Promise?

在 JavaScript 中,许多操作都是异步的,例如网络请求、文件读取和定时器等。传统的回调函数虽然可以处理这些异步操作,但容易导致“回调地狱”,使得代码难以维护和理解。Promise 提供了一种更优雅的方式来处理异步操作,使代码更加清晰和易读。

Promise 的三大状态

  • Pending(进行中):初始状态,既不是成功也不是失败。
  • Fulfilled(已成功):操作成功完成。
  • Rejected(已失败):操作失败。

通过这些状态,Promise 提供了一个统一的异步编程接口,使得异步代码的编写和管理变得更加简单。

创建 Promise

在 TypeScript 中,你可以使用 Promise 构造函数来创建一个 Promise 对象。构造函数接受一个执行器函数作为参数,该函数有两个参数:resolve 和 reject,分别用于表示异步操作的成功和失败。

const promise = new Promise
<string>((resolve, reject) => {
  // 模拟异步操作
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve("成功!");
    } else {
      reject("失败!");
    }
  }, 1000);
});

promise.then(result => {
  console.log(`完成: ${result}`);
}).catch(error => {
  console.error(`错误: ${error}`);
});

在这个例子中,我们创建了一个 Promise,模拟了一个异步操作。如果操作成功,调用 resolve 方法;如果失败,调用 reject 方法。then 方法用于处理成功的结果,catch 方法用于处理失败的情况。

Promise 链式调用

Promise 的一个强大特性是链式调用。then 和 catch 方法都会返回一个新的 Promise,这使得我们可以按顺序执行多个异步操作。

const promise = Promise.resolve(1);

promise
  .then(n => {
    return n * 2; // 返回 2
  })
  .then(n => {
    return n + 10; // 返回 12
  })
  .then(n => {
    console.log(`最终结果: ${n}`); // 输出: 最终结果: 12
  });

在这个例子中,我们通过链式调用 then 方法,依次执行多个异步操作。每个 then 方法都会返回一个新的 Promise,这样就可以确保操作按顺序执行。

Promise.all

Promise.all 方法用于等待所有 Promise 完成,并返回一个包含所有结果的数组。如果任何一个 Promise 失败,Promise.all 会立即拒绝,并返回第一个失败的 Promise 的原因。

const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);

Promise.all([p1, p2, p3])
  .then(results => {
    console.log(`全部完成: ${results}`); // 输出: 全部完成: 1,2,3
    console.log(`总和: ${results.reduce((a, b) => a + b, 0)}`); // 输出: 总和: 6
  })
  .catch(error => {
    console.error(`错误: ${error}`);
  });

在这个例子中,我们创建了三个 Promise,并使用 Promise.all 等待它们全部完成。如果所有 Promise 都成功,then 方法会接收到一个包含所有结果的数组。

Promise.race

Promise.race 方法返回最先完成的 Promise 的结果,无论是成功还是失败。

const p1 = new Promise(resolve => setTimeout(() => resolve("p1"), 100));
const p2 = new Promise(resolve => setTimeout(() => resolve("p2"), 50));
const p3 = new Promise(resolve => setTimeout(() => resolve("p3"), 30));

Promise.race([p1, p2, p3])
  .then(value => {
    console.log(`最先完成: ${value}`); // 输出: 最先完成: p3
  })
  .catch(error => {
    console.error(`错误: ${error}`);
  });

在这个例子中,我们创建了三个具有不同延迟时间的 Promise,并使用 Promise.race 等待最先完成的那个。p3 由于延迟时间最短,因此最先完成。

Promise.allSettled

Promise.allSettled 方法等待所有 Promise 结束,无论成功还是失败,并返回每个 Promise 的状态和结果。

const p1 = Promise.resolve("成功");
const p2 = Promise.reject(new Error("失败"));
const p3 = Promise.resolve("完成");

Promise.allSettled([p1, p2, p3])
  .then(results => {
    results.forEach((result, index) => {
      if (result.status === "fulfilled") {

        console.log(`Promise ${index}: ${result.value}`);
      } else {
        console.log(`Promise ${index}: ${result.reason.message}`);
      }
    });
  })
  .catch(error => {
    console.error(`错误: ${error}`);
  });

在这个例子中,我们创建了三个 Promise,其中一个会失败。使用 Promise.allSettled 等待所有 Promise 结束后,我们可以通过 results 数组获取每个 Promise 的状态和结果。

Promise 类型注解

TypeScript 的泛型支持使得 Promise 的类型声明更加精确。通过泛型参数,可以指定 Promise 解决值和拒绝值的类型。

function getUser(): Promise<{ name: string; age: number }> {
  return Promise.resolve({ name: "Alice", age: 25 });
}

async function main() {
  try {
    const user = await getUser();
    console.log(`用户: ${JSON.stringify(user)}`); // 输出: 用户: {"name":"Alice","age":25}
  } catch (error) {
    console.error(`错误: ${error}`);
  }
}

main();

在这个例子中,我们定义了一个返回 Promise<{ name: string; age: number }> 的函数 getUser。使用 async/await 语法可以更简洁地处理异步操作,并且 TypeScript 会自动推断 user 的类型。

注意事项

  • 泛型参数:始终为 Promise 指定泛型参数,明确返回类型。
  • 错误处理:记得使用 catch 处理 Promise 失败的情况。
  • all vs allSettled:需要全部结果时用 allSettled,需要快速失败时用 all。
  • async/await:现代代码推荐使用 async/await,语法更简洁。

总结

Promise 是 TypeScript 异步编程的核心工具。通过本文的介绍,你应该已经掌握了 Promise 的基本概念、常见用法以及最佳实践。以下是一些关键点的总结:

  • Promise:异步操作容器,有 pending、fulfilled 和 rejected 三种状态。
  • then/catch:链式处理异步结果。
  • Promise.all:等待全部完成,任一失败则整体失败。
  • Promise.race:返回最先完成的结果。
  • Promise.allSettled:等待全部结束,返回每个的状态。

建议在实际开发中优先使用 async/await 语法,它本质上还是基于 Promise,但写起来像同步代码,既类型安全又易于阅读。希望本文能帮助你在 TypeScript 项目中更好地管理和编写异步代码。