当前位置: 首页 > news >正文

【前端基础】Promise 详解

文章目录

    • 什么是 Promise?
    • 为什么要使用 Promise?
    • 创建 Promise
    • 消费 Promise (使用 Promise)
      • 1. `.then(onFulfilled, onRejected)`
      • 2. `.catch(onRejected)`
      • 3. `.finally(onFinally)`
    • Promise 链 (Promise Chaining)
    • Promise 的静态方法
      • 1. `Promise.resolve(value)`
      • 2. `Promise.reject(reason)`
      • 3. `Promise.all(iterable)`
      • 4. `Promise.allSettled(iterable)` (ES2020)
      • 5. `Promise.race(iterable)`
      • 6. `Promise.any(iterable)` (ES2021)
    • Promise 中的错误处理
    • Async/Await (基于 Promise 的语法糖)
    • 常见陷阱和最佳实践

在这里插入图片描述

Promise 是现代 JavaScript 中处理异步操作的核心机制,它极大地改善了回调函数(callback)带来的“回调地狱”问题,使异步代码更加清晰、易于管理和维护。

什么是 Promise?

Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。简单来说,Promise 是一个承诺:它承诺在未来的某个时刻会给你一个结果,这个结果要么是成功的(fulfilled/resolved),要么是失败的(rejected)。

一个 Promise 对象必然处于以下三种状态之一:

  1. Pending (进行中):初始状态,既没有被兑现,也没有被拒绝。异步操作正在进行中。
  2. Fulfilled (已兑现/已成功):意味着异步操作成功完成。Promise 有一个与之关联的值(称为 resolved value)。
  3. Rejected (已拒绝/已失败):意味着异步操作失败。Promise 有一个与之关联的原因(通常是一个 Error 对象,称为 rejection reason)。

一旦 Promise 的状态从 pending 变为 fulfilled 或 rejected,它的状态和结果就固定不变了,不能再次改变。这种状态转变是单向的。我们称一个 Promise 为 settled (已敲定),如果它不是 pending 状态(即已兑现或已拒绝)。

为什么要使用 Promise?

  1. 告别回调地狱 (Callback Hell)
    当多个异步操作相互依赖时,使用传统回调函数很容易形成层层嵌套的结构,代码难以阅读和维护。Promise 通过链式调用 .then() 来扁平化这种结构。

    // 回调地狱示例
    asyncOperation1(data1, (err1, result1) => {if (err1) { /* ... */ }asyncOperation2(result1, (err2, result2) => {if (err2) { /* ... */ }asyncOperation3(result2, (err3, result3) => {// ...});});
    });// 使用 Promise
    asyncOperation1(data1).then(result1 => asyncOperation2(result1)).then(result2 => asyncOperation3(result2)).then(result3 => { /* ... */ }).catch(error => { /* 处理任何步骤中的错误 */ });
    
  2. 更优雅的错误处理
    Promise 提供了 .catch() 方法,可以集中处理链中任何一个环节发生的错误,而不需要在每个回调中单独处理。

  3. 更好的代码可读性和可维护性
    链式调用使得异步操作的流程更加清晰,代码结构更接近同步代码的思维方式。

  4. 组合异步操作
    Promise 提供了一些静态方法(如 Promise.all(), Promise.race())来组合多个 Promise,实现更复杂的异步逻辑。

创建 Promise

可以通过 new Promise(executor) 构造函数来创建一个 Promise 对象。executor 是一个函数,它接收两个参数:resolvereject。这两个参数本身也是函数。

  • executor 函数会立即同步执行
  • executor 函数内部,通常会执行一些异步操作。
  • 当异步操作成功时,调用 resolve(value),将 Promise 的状态变为 fulfilled,并将 value 作为其结果。
  • 当异步操作失败时,调用 reject(reason),将 Promise 的状态变为 rejected,并将 reason (通常是一个 Error 对象) 作为其原因。
const myPromise = new Promise((resolve, reject) => {// 模拟一个异步操作console.log("Promise executor 开始执行"); // 这行会立即打印setTimeout(() => {const success = Math.random() > 0.5; // 随机成功或失败if (success) {resolve("操作成功!数据来了!"); // 成功时调用 resolve} else {reject(new Error("操作失败了,呜呜呜...")); // 失败时调用 reject}}, 1000);
});console.log("Promise 已创建"); // 这行也会在 executor 内的 setTimeout 之前打印

消费 Promise (使用 Promise)

创建了 Promise之后,我们需要使用它的结果。这主要通过以下几个方法:

1. .then(onFulfilled, onRejected)

.then() 方法接收两个可选的函数作为参数:

  • onFulfilled:当 Promise 状态变为 fulfilled (成功) 时被调用,接收 Promise 的解决值作为参数。
  • onRejected:当 Promise 状态变为 rejected (失败) 时被调用,接收 Promise 的拒绝原因作为参数。

关键点.then() 方法返回一个新的 Promise。这使得链式调用成为可能。

myPromise.then((value) => { // onFulfilledconsole.log("成功回调:", value);return "处理成功后的新值"; // 这个返回值会成为下一个 .then 的 onFulfilled 的参数},(error) => { // onRejectedconsole.error("失败回调 (在 then 的第二个参数中):", error.message);// 如果在这里处理了错误,并且没有重新抛出或返回一个 rejected Promise,// 那么链条会继续走向 fulfilled 状态// throw error; // 如果想让链条继续保持 rejected 状态return "从错误中恢复的值";}).then((nextValue) => {console.log("下一个成功回调:", nextValue);},(nextError) => {console.error("下一个失败回调:", nextError.message);});

2. .catch(onRejected)

.catch(onRejected) 方法仅仅是 .then(null, onRejected) 的语法糖。它用于捕获 Promise 链中任何未被处理的拒绝 (rejection)。通常放在 Promise 链的末尾,用于统一处理错误。

myPromise.then(value => {console.log("成功:", value);if (value.includes("数据")) {throw new Error("成功了,但我想抛个错试试!"); // 模拟在 then 中抛错}return value;}).catch(error => { // onRejectedconsole.error("Catch 捕获到错误:", error.message);// 错误在这里被捕获和处理// 如果不重新 throw,链条会认为错误已处理,后续的 .then (如果有 onFulfilled) 可能会执行});

3. .finally(onFinally)

.finally(onFinally) 方法接收一个回调函数 onFinally,无论 Promise 是 fulfilled 还是 rejected,这个回调都会被执行。

  • onFinally 回调不接收任何参数。
  • 它通常用于执行一些清理工作,比如隐藏加载指示器、关闭数据库连接等。
  • .finally() 也会返回一个新的 Promise,它会采用原始 Promise 的状态和值/原因 (除非 onFinally 抛出错误或返回一个被拒绝的 Promise)。
myPromise.then(value => console.log("Finally 示例 - 成功:", value)).catch(error => console.error("Finally 示例 - 失败:", error.message)).finally(() => {console.log("Finally: 操作已完成,无论成功或失败都会执行我!");// 通常不在这里改变 Promise 的最终结果});

Promise 链 (Promise Chaining)

由于 .then(), .catch(), 和 .finally() 都返回新的 Promise 对象,我们可以将它们串联起来,形成一个 Promise 链。

  • 传递值:前一个 .thenonFulfilled 回调的返回值会作为下一个 .thenonFulfilled 回调的参数。
  • 异步操作的串行化:如果 .then 的回调函数返回一个新的 Promise,那么链条会等待这个新的 Promise 完成后,才会继续执行后续的 .then
function step1() {return new Promise(resolve => {setTimeout(() => {console.log("步骤1完成");resolve("来自步骤1的结果");}, 500);});
}function step2(dataFromStep1) {return new Promise(resolve => {setTimeout(() => {console.log("步骤2完成,接收到:", dataFromStep1);resolve("来自步骤2的结果");}, 500);});
}function step3(dataFromStep2) {return new Promise(resolve => {setTimeout(() => {console.log("步骤3完成,接收到:", dataFromStep2);resolve("最终结果");}, 500);});
}step1().then(result1 => step2(result1)) // step2 返回一个 Promise.then(result2 => step3(result2)) // step3 返回一个 Promise.then(finalResult => {console.log("所有步骤完成:", finalResult);}).catch(error => {console.error("链条中发生错误:", error);});

Promise 的静态方法

Promise 对象本身也提供了一些有用的静态方法:

1. Promise.resolve(value)

返回一个使用给定值解决的 Promise 对象。

  • 如果 value 本身是一个 Promise,则返回这个 Promise。
  • 如果 value 是一个 thenable 对象(即拥有 .then 方法的对象),Promise.resolve 会尝试展开这个 thenable 对象,并采用其最终状态。
  • 否则,返回的 Promise 将会以 value 值完成。
Promise.resolve("立即成功").then(v => console.log(v)); // 输出: 立即成功const p = new Promise(r => setTimeout(() => r("延迟成功"), 100));
Promise.resolve(p).then(v => console.log(v)); // 等待 p 完成后输出: 延迟成功

2. Promise.reject(reason)

返回一个以给定原因拒绝的 Promise 对象。

Promise.reject(new Error("立即失败")).catch(e => console.error(e.message)); // 输出: 立即失败

3. Promise.all(iterable)

接收一个 Promise 对象的可迭代对象 (例如数组) 作为参数。

  • 返回一个新的 Promise。
  • 这个新的 Promise 只有在可迭代对象中所有的 Promise 都成功 (fulfilled) 时才会成功。其成功的值是一个数组,包含了所有输入 Promise 的成功值,顺序与输入 Promise 在可迭代对象中的顺序一致。
  • 如果可迭代对象中的任何一个 Promise 失败 (rejected),那么 Promise.all() 返回的 Promise 会立即失败,并以第一个失败的 Promise 的原因为准。
const promise1 = Promise.resolve(3);
const promise2 = 42; // 会被 Promise.resolve(42) 包装
const promise3 = new Promise((resolve, reject) => {setTimeout(resolve, 100, 'foo');
});Promise.all([promise1, promise2, promise3]).then((values) => {console.log("Promise.all 成功:", values); // 输出: [3, 42, "foo"]
}).catch(error => {console.error("Promise.all 失败:", error);
});const promiseWithError = Promise.reject('错误了');
Promise.all([promise1, promiseWithError, promise3]).then((values) => {// 这段不会执行
}).catch(error => {console.error("Promise.all 因为一个错误而失败:", error); // 输出: 错误了
});

4. Promise.allSettled(iterable) (ES2020)

接收一个 Promise 对象的可迭代对象作为参数。

  • 返回一个新的 Promise。
  • 这个新的 Promise 会在可迭代对象中所有的 Promise 都已敲定 (settled) 后(无论是 fulfilled 还是 rejected)才会完成。
  • 其成功的值是一个对象数组,每个对象描述了对应 Promise 的结果,格式为:
    • 成功:{ status: 'fulfilled', value: resolvedValue }
    • 失败:{ status: 'rejected', reason: rejectionReason }

这对于你希望等待多个异步操作完成,但不在乎它们是否都成功的情况非常有用。

const pSuccess = Promise.resolve("成功的值");
const pFailure = Promise.reject("失败的原因");
const pPending = new Promise(r => setTimeout(() => r("稍后成功"), 200));Promise.allSettled([pSuccess, pFailure, pPending]).then(results => {results.forEach(result => {if (result.status === 'fulfilled') {console.log(`AllSettled - Fulfilled: ${result.value}`);} else {console.error(`AllSettled - Rejected: ${result.reason}`);}});// 输出:// AllSettled - Fulfilled: 成功的值// AllSettled - Rejected: 失败的原因// (等待200ms后) AllSettled - Fulfilled: 稍后成功
});

5. Promise.race(iterable)

接收一个 Promise 对象的可迭代对象作为参数。

  • 返回一个新的 Promise。
  • 这个新的 Promise 会在可迭代对象中的任何一个 Promise 最先敲定 (settled) 时立即敲定,并采用第一个敲定的 Promise 的状态和结果(无论是 fulfilled 还是 rejected)。
const pFast = new Promise((resolve) => setTimeout(() => resolve('快速'), 50));
const pSlow = new Promise((resolve) => setTimeout(() => resolve('慢速'), 200));
const pReject = new Promise((resolve, reject) => setTimeout(() => reject('立即拒绝'), 10));Promise.race([pFast, pSlow]).then(value => console.log("Race 胜出 (fast/slow):", value)) // 输出: 快速.catch(error => console.error("Race 失败 (fast/slow):", error));Promise.race([pFast, pReject]).then(value => console.log("Race 胜出 (fast/reject):", value)).catch(error => console.error("Race 失败 (fast/reject):", error)); // 输出: 立即拒绝

6. Promise.any(iterable) (ES2021)

接收一个 Promise 对象的可迭代对象作为参数。

  • 返回一个新的 Promise。
  • 这个新的 Promise 会在可迭代对象中的任何一个 Promise 最先成功 (fulfilled) 时立即成功,并以第一个成功的 Promise 的值为准。
  • 如果可迭代对象中的所有 Promise 都失败 (rejected),那么 Promise.any() 返回的 Promise 会失败,并以一个 AggregateError 对象为原因。AggregateError 对象有一个 errors 属性,它是一个包含了所有拒绝原因的数组。
const pErr1 = Promise.reject('第一个错误');
const pErr2 = Promise.reject('第二个错误');
const pSucc1 = new Promise(r => setTimeout(() => r('第一个成功'), 50));
const pSucc2 = new Promise(r => setTimeout(() => r('第二个成功'), 10));Promise.any([pErr1, pSucc1, pSucc2]).then(value => console.log("Any 成功:", value)) // 输出: 第二个成功 (因为它最先 fulfilled).catch(error => console.error("Any 失败:", error));Promise.any([pErr1, pErr2]).then(value => console.log("Any 成功 (全失败场景):", value)) // 不会执行.catch(error => {console.error("Any 失败 (全失败场景):", error.name); // AggregateErrorconsole.error(error.errors); // ['第一个错误', '第二个错误']});

Promise 中的错误处理

正确处理 Promise 中的错误至关重要。

  1. 使用 .catch()
    最佳实践是在 Promise 链的末尾添加一个 .catch() 来捕获所有未处理的拒绝。

  2. .then() 的第二个参数
    虽然 .then(onFulfilled, onRejected) 可以处理错误,但它不如 .catch() 清晰,且如果 onFulfilled 抛出错误,该错误不会被同一个 .thenonRejected 捕获,而是会传递给链中的下一个 .catch()onRejected

  3. Executor 中的同步错误
    如果在 new Promise(executor)executor 函数中抛出同步错误,这个错误会被隐式捕获,并导致 Promise 立即变为 rejected 状态。

    new Promise((resolve, reject) => {throw new Error("Executor 中的同步错误");
    }).catch(err => console.error(err.message)); // 输出: Executor 中的同步错误
    
  4. 未捕获的 Promise 拒绝 (Unhandled Rejection)
    如果一个 Promise 被拒绝,并且链上没有任何 .catch()onRejected 处理它,浏览器通常会在控制台报告一个 “Uncaught (in promise)” 错误。Node.js 环境中,这可能会导致应用进程终止(取决于版本和配置)。所以,务必处理所有可能的拒绝

Async/Await (基于 Promise 的语法糖)

ES2017 引入了 asyncawait 关键字,它们是构建在 Promise 之上的语法糖,使得异步代码的写法更像同步代码,从而更加直观。

  • async function:声明一个异步函数。异步函数会自动返回一个 Promise。如果函数内部返回一个非 Promise 值,这个值会被 Promise.resolve() 包装。如果函数内部抛出错误,返回的 Promise 会被 reject。
  • await:只能在 async function 内部使用。它会暂停异步函数的执行,等待 await 后面的 Promise 完成(resolved 或 rejected)。
    • 如果 Promise resolved,await 表达式的值就是 Promise 的解决值。
    • 如果 Promise rejected,await 会抛出这个拒绝的原因(通常是一个 Error 对象),这可以被 async function 内部的 try...catch 语句捕获。
function fetchData(shouldFail = false) {return new Promise((resolve, reject) => {setTimeout(() => {if (shouldFail) {reject(new Error("数据获取失败 (async/await)"));} else {resolve("数据已获取 (async/await)");}}, 1000);});
}async function processData() {console.log("开始处理数据...");try {const data1 = await fetchData(); // 等待 fetchData() 完成console.log("第一份数据:", data1);const data2 = await fetchData(true); // 尝试获取会失败的数据console.log("第二份数据:", data2); // 这行不会执行} catch (error) {console.error("Async/await 捕获到错误:", error.message);} finally {console.log("数据处理流程结束 (async/await)");}
}processData();
// 输出顺序:
// 开始处理数据...
// (等待1秒)
// 第一份数据: 数据已获取 (async/await)
// (等待1秒)
// Async/await 捕获到错误: 数据获取失败 (async/await)
// 数据处理流程结束 (async/await)

async/await 极大地简化了异步流程控制,尤其是当涉及到多个 последовательных (sequential) 异步操作时。

常见陷阱和最佳实践

  1. 忘记 return:在 .then() 回调中,如果进行了异步操作并创建了新的 Promise,或者想将值传递给下一个 .then(),确保 return 这个 Promise 或值。否则,链条可能会意外地提前继续,或者下一个 .then() 接收到 undefined

  2. “吞没”错误:在 .catch()onRejected 中,如果没有显式地重新抛出错误 (throw error;) 或返回一个被拒绝的 Promise,那么 Promise 链会认为错误已经被处理,并可能转为 fulfilled 状态。

  3. 不必要的 Promise 包装:避免所谓的 “Promise constructor anti-pattern”,即在 new Promise 的 executor 中包装已经返回 Promise 的函数。

    // 反模式
    function getUserDataWrapper(userId) {return new Promise((resolve, reject) => { // 不必要的 Promise 包装fetch(`/api/users/${userId}`) // fetch 本身返回 Promise.then(response => response.json()).then(data => resolve(data)).catch(error => reject(error));});
    }// 更好的方式
    function getUserData(userId) {return fetch(`/api/users/${userId}`).then(response => {if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);}return response.json();});
    }
    
  4. 总是添加 .catch():对于任何 Promise 链,都应该在末尾添加一个 .catch() 来处理潜在的未捕获错误。

  5. Promise.all vs Promise.allSettled:根据你的需求选择。如果任何一个失败都应该导致整个操作失败,使用 Promise.all。如果希望所有操作都尝试完成,并分别处理它们的结果,使用 Promise.allSettled

  6. 理解微任务队列:Promise 的 .then, .catch, .finally 的回调会作为微任务 (microtask) 被放入微任务队列中,它们会在当前宏任务 (macrotask) 执行完毕后、下一个宏任务开始前立即执行。这对于理解 Promise 的执行时机非常重要。

Promise 是 JavaScript 异步编程的基石,深刻理解它对于编写健壮、可维护的现代 JavaScript 应用至关重要。

http://www.xdnf.cn/news/664525.html

相关文章:

  • FacePoke创意交互实战:Cpolar技术赋能远程人像编辑的趣味实现
  • 国内短剧 vs. 海外短剧系统开发:2025年SEO优化与市场策略全解析
  • 机械设计插件
  • MS1824+MS7210+MS2130 1080P@60Hz USB3.0采集
  • 【文献阅读】Mixture of Lookup Experts
  • 语音识别技术在人工智能中的应用
  • 03 环境变量和标签
  • 电子元器件散热方式
  • 医院门户网站群改版技术白皮书
  • 如何调试CATIA CAA程序导致的CATIA异常崩溃问题
  • Vue 3 核心知识点全览
  • 电子电气架构 -- 第五代汽车电子电气(E/E)架构的两种主导实施方式
  • c++ 二叉搜索树(BinarySearchTree)
  • 晚期NSCLC临床试验终点与分析策略
  • 【力扣】关于链表索引
  • 初识LangChain
  • Visual Studio 调试中 PDB 与图像不匹配
  • STM32F103_Bootloader程序开发03 - 启动入口与升级模式判断(boot_entry.c与boot_entry.h)
  • JetsonHacksNano RealSense自动安装脚本文件解析
  • 公链开发全生态:技术架构、生态构建与未来图景
  • 环境配置相关问题以及解决方案
  • JavaScripts 常见误区
  • 小刚说C语言刷题—1152 - 求n个数的最大值和最小值
  • mybatis-plus动态分页
  • ARM架构
  • 密钥分发与公钥证书
  • 工厂方法模式之Factory Method(工厂方法)
  • Python网络编码——聊天小工具
  • 2025年中国ERP软件前十名对比:选型指南与适用场景的分析
  • 数控滑台技术革新:提升生产效率的关键