在 Node.js 异步编程中,Promise 用来表达异步操作的最终结果,并通过 then/catch 组织成功与失败分支,避免回调嵌套过深。原文从状态、创建、链式调用、静态方法、错误处理到 async/await 给出了一套入门示例。下面按脚本开发中实际会遇到的代码组织方式来重组这些知识点,代码逻辑和输出结论保持与原文一致。
一、状态与创建方式
Promise 有三种状态:Pending(等待中)、Fulfilled(已成功)、Rejected(已失败)。状态一旦从 Pending 变为 Fulfilled 或 Rejected,就不会再改变。创建 Promise 时传入执行器,由 resolve 和 reject 改变状态:- const myPromise = new Promise((resolve, reject) => {
- // 异步操作
- const success = true; // 假设这是异步操作的结果
- if (success) {
- resolve('操作成功!'); // 状态变为 fulfilled
- } else {
- reject('操作失败!'); // 状态变为 rejected
- }
- });
复制代码 这段示例中的 success 只是模拟异步结果;真实脚本里通常把文件读写、网络请求等异步结果交给 resolve 或 reject。
二、then、catch 与 finally
使用 .then() 处理成功结果,.catch() 处理失败,.finally() 在成功或失败后都会执行,适合做资源清理:- myPromise
- .then((result) => {
- console.log(result); // 输出:操作成功!
- })
- .catch((error) => {
- console.error(error); // 输出:操作失败!
- })
- .finally(() => {
- console.log('操作完成(无论成功或失败)');
- });
复制代码 这里 finally 的回调不改变 Promise 的结果,原文把它定位为最终清理操作。
三、链式调用组织多个异步步骤
当多个异步操作有先后依赖时,可以让 then 返回下一个 Promise,使多个步骤串成链:- function asyncOperation1() {
- return new Promise((resolve) => {
- setTimeout(() => resolve('第一步完成'), 1000);
- });
- }
- function asyncOperation2(data) {
- return new Promise((resolve) => {
- setTimeout(() => resolve(`${data}, 第二步完成`), 1000);
- });
- }
- asyncOperation1()
- .then((result) => asyncOperation2(result))
- .then((finalResult) => {
- console.log(finalResult); // 输出:第一步完成, 第二步完成
- })
- .catch((error) => {
- console.error('链式中出错:', error);
- });
复制代码 这种写法把嵌套回调改成横向链路。关键点是 then 中返回 Promise,后续 then 才能拿到下一步结果。
四、并发组合静态方法
Promise.all()、Promise.allSettled()、Promise.race()、Promise.any() 用来处理多个 Promise 的组合结果,触发失败的条件不同。
Promise.all() 等待全部成功,任意一个失败就进入失败分支:- const promise1 = Promise.resolve('第一个');
- const promise2 = Promise.resolve('第二个');
- Promise.all([promise1, promise2])
- .then((results) => {
- console.log(results); // 输出:['第一个', '第二个']
- })
- .catch((error) => {
- console.error('其中一个失败:', error);
- });
复制代码 Promise.allSettled() 等待所有 Promise 完成,不管成功还是失败,然后逐个检查 status:- const promise1 = Promise.resolve('成功');
- const promise2 = Promise.reject('失败');
- Promise.allSettled([promise1, promise2])
- .then((results) => {
- results.forEach(result => {
- if (result.status === 'fulfilled') {
- console.log('成功:', result.value);
- } else {
- console.log('失败:', result.reason);
- }
- });
- });
复制代码 Promise.race() 返回最先完成的 Promise;示例中第二个定时器更短,因此输出第二个:- const promise1 = new Promise((resolve) => setTimeout(resolve, 500, '第一个'));
- const promise2 = new Promise((resolve) => setTimeout(resolve, 100, '第二个'));
- Promise.race([promise1, promise2])
- .then((result) => {
- console.log(result); // 输出:第二个
- });
复制代码 Promise.any() 返回第一个成功的 Promise,失败项会被忽略;只有全部失败时才进入 catch:- const promise1 = Promise.reject('失败1');
- const promise2 = Promise.resolve('成功2');
- Promise.any([promise1, promise2])
- .then((result) => {
- console.log(result); // 输出:成功2
- })
- .catch((error) => {
- console.log('所有都失败了:', error);
- });
复制代码 从原文对比可归纳:all 是任意失败即失败;allSettled 永不因单个失败而失败;race 由最先完成者决定,最先失败则失败;any 只在全部失败时失败。选用哪个方法,取决于脚本要“全部成功才继续”“收集全部结果”还是“取最快结果/取首个成功结果”。
五、错误处理写法与链式 catch
Promise 错误处理有两种常见写法:使用 .catch(),或使用 .then() 的第二个参数:- someAsyncFunction()
- .then((result) => { /* 处理成功 */ })
- .catch((error) => { /* 处理所有错误 */ });
- someAsyncFunction()
- .then(
- (result) => { /* 处理成功 */ },
- (error) => { /* 处理失败 */ }
- );
复制代码 原文强调,如果中间 then 中抛出错误,后面的 catch 会捕获:- someAsyncFunction()
- .then((result) => {
- throw new Error('中间处理出错');
- })
- .catch((error) => {
- console.error('捕获到:', error);
- });
复制代码 因此链式调用时,推荐在所有 then 之后只放一个 catch,把流程中的错误集中处理:- fetchData()
- .then(processData)
- .then(saveData)
- .then(displayResult)
- .catch(error => console.error('流程中出错:', error));
复制代码 这段代码中的四个函数在原示例中作为流程节点示意,实际项目里应保证它们返回可继续链式处理的值或 Promise。
六、async/await 改写
ES2017 引入 async/await,让 Promise 的使用更接近同步代码。原文示例用 try/catch 包住 await 调用:- async function runOperations() {
- try {
- const result1 = await asyncOperation1();
- const result2 = await asyncOperation2(result1);
- console.log(result2);
- } catch (error) {
- console.error(error);
- }
- }
- runOperations();
复制代码 这里的两个 await 表示按顺序等待。原文提示 Promise 是现代 Node.js 异步编程的基础,理解 Promise 是掌握 async/await 的前提;实际编写时可优先用 async/await 让异步逻辑更清晰、更易维护。
七、把回调函数转成 Promise
Node.js 老式 API 常采用回调风格。原文先手写 Promise 包装 fs.readFile:- const fs = require('fs');
- function readFilePromise(filePath) {
- return new Promise((resolve, reject) => {
- fs.readFile(filePath, 'utf8', (err, data) => {
- if (err) {
- reject(err);
- } else {
- resolve(data);
- }
- });
- });
- }
- readFilePromise('example.txt')
- .then(data => console.log(data))
- .catch(err => console.error(err));
复制代码 随后可以用 Node.js 内置的 util.promisify 简化转换:- const fs = require('fs');
- const util = require('util');
- const readFilePromise = util.promisify(fs.readFile);
- const writeFilePromise = util.promisify(fs.writeFile);
- readFilePromise('example.txt', 'utf8')
- .then(data => {
- console.log(data);
- return writeFilePromise('output.txt', data);
- })
- .then(() => console.log('写入完成'))
- .catch(err => console.error(err));
复制代码 这段流程先读 example.txt,再把读取内容写入 output.txt;每一步返回 Promise,错误统一落到 catch。
八、开发中的实践清单
结合原文最佳实践和示例,可整理为:总是处理错误,不忽略 catch 或 try-catch;避免 Promise 嵌套,用链式调用或 async/await 保持代码扁平;给 Promise 变量起有意义的名称;在函数中返回 Promise,方便调用方继续链式处理;用 finally 做清理,无论成功或失败都执行。状态、创建、then/catch、链式调用、静态方法、async/await 和 util.promisify 构成了原文的入门主线。对 Node.js 脚本而言,先明确异步步骤是串行还是并发,再选择 then 链、async/await 或 Promise.all 等组合方法,能减少回调嵌套并让错误出口更集中。 |