一、Promise 笔记复习
1.1 什么是 Promise?
Promise 字面意思是“承诺”,它代表一个异步操作的最终完成(或失败)及其结果值。简单来说,Promise 就是一个容器,里面存放着一个未来才会结束的异步操作(比如网络请求、定时器、文件读取等)。
Promise 有三个核心状态,且状态一旦改变,就永久固定,无法再次修改:
- pending(等待中) :初始状态,异步操作尚未完成,既没有成功也没有失败。
- fulfilled(已成功) :异步操作完成,Promise 获得结果值,状态从 pending 转为 fulfilled。
- rejected(已失败) :异步操作失败,Promise 获得错误信息,状态从 pending 转为 rejected。
注意:状态只能从 pending → fulfilled,或 pending → rejected,不能反向切换,也不能在 fulfilled/rejected 状态间切换。
1.2 Promise 基本语法
Promise 是一个构造函数,通过 new 关键字创建实例,接收一个回调函数( executor 执行器)作为参数,该回调函数有两个内置参数:resolve 和 reject。
// 基本语法
const promise = new Promise((resolve, reject) => {
// 异步操作(比如网络请求、定时器)
setTimeout(() => {
const success = true;
if (success) {
// 操作成功:调用 resolve,将状态转为 fulfilled,并传递结果值
resolve("操作成功的结果");
} else {
// 操作失败:调用 reject,将状态转为 rejected,并传递错误信息
reject(new Error("操作失败的原因"));
}
}, 1000);
});
- resolve:函数,用于将 Promise 状态转为 fulfilled,参数为“成功结果”,可以是任意类型(基本类型、对象、甚至另一个 Promise)。
- reject:函数,用于将 Promise 状态转为 rejected,参数为“错误信息”,通常是 Error 对象,便于后续捕获和处理错误。
1.3 简单使用
then 方法用于处理 fulfilled 状态的结果,接收两个可选参数:
- 第一个参数:fulfilled 状态的回调函数,接收 resolve 传递的成功结果。
- 第二个参数:rejected 状态的回调函数,接收 reject 传递的错误信息(可选,通常用 catch 方法替代,更清晰)。
then 方法的返回值是一个新的 Promise 实例,因此可以实现链式调用,解决回调地狱。
promise.then(
(result) => {
console.log("成功:", result); // 输出:成功:操作成功的结果
},
(error) => {
console.log("失败:", error.message); // 若失败,输出:失败:操作失败的原因
}
);
// 链式调用示例
promise
.then((result) => {
console.log("第一步成功:", result);
return result + ",继续下一步"; // 返回值会作为下一个 then 的参数
})
.then((newResult) => {
console.log("第二步成功:", newResult); // 输出:第二步成功:操作成功的结果,继续下一步
});
catch 方法专门用于处理 rejected 状态的错误,接收一个回调函数,参数为 reject 传递的错误信息。
作用:统一捕获 Promise 链条中的所有错误(包括 then 方法中抛出的错误),比 then 的第二个参数更简洁、更易维护。
promise
.then((result) => {
console.log("成功:", result);
// 若此处抛出错误,也会被 catch 捕获
throw new Error("then 中出现的错误");
})
.catch((error) => {
console.log("错误:", error.message); // 捕获所有失败/错误
});
finally 方法用于指定一个“无论 Promise 状态是 fulfilled 还是 rejected,都会执行”的回调函数,通常用于做一些收尾操作(比如关闭加载动画、释放资源)。
注意:finally 不接收任何参数,也不改变 Promise 的状态和结果。
promise
.then((result) => {
console.log("成功:", result);
})
.catch((error) => {
console.log("错误:", error.message);
})
.finally(() => {
console.log("异步操作结束,无论成功失败都执行"); // 必执行
});
二、Promise 知识点
2.1 Promise 方法(all/allSettled/race/resolve/reject)
Promise 构造函数上有五个静态方法,用于处理多个 Promise 实例的组合场景,是实际开发中高频使用的技巧。
2.1.1 Promise.all()
接收一个 Promise 数组作为参数,返回一个新的 Promise:
- 只有所有 Promise 都变为 fulfilled,新 Promise 才会变为 fulfilled,结果是所有 Promise 成功结果组成的数组(顺序与传入数组一致)。
- 只要有一个 Promise 变为 rejected,新 Promise 立即变为 rejected,结果是第一个 rejected 的错误信息。
多个异步操作必须全部成功才能继续执行 比如同时请求多个接口,全部返回数据后再渲染页面。
// 模拟两个接口请求
const request1 = new Promise((resolve) => setTimeout(() => resolve("接口1数据"), 1000));
const request2 = new Promise((resolve) => setTimeout(() => resolve("接口2数据"), 2000));
Promise.all([request1, request2])
.then((results) => {
console.log("所有请求成功:", results); // 2秒后输出:["接口1数据", "接口2数据"]
})
.catch((error) => {
console.log("有请求失败:", error);
});
2.1.2 Promise.allSettled()
接收一个 Promise 数组作为参数,返回一个新的 Promise,无论所有 Promise 成功还是失败,新 Promise 都会变为 fulfilled。
结果是一个数组,每个元素对应一个 Promise 的结果,包含两个属性:
- status:"fulfilled" 或 "rejected",表示当前 Promise 的状态。
- value:若 status 为 fulfilled,是成功结果;若为 rejected,是错误信息。
适用场景:多个异步操作,无论成功失败,都需要知道每个操作的结果(比如批量提交表单,需要统计成功和失败的数量)。
const request1 = new Promise((resolve) => setTimeout(() => resolve("接口1成功"), 1000));
const request2 = new Promise((reject) => setTimeout(() => reject("接口2失败"), 2000));
Promise.allSettled([request1, request2]).then((results) => {
console.log("所有操作结果:", results);
// 输出:
// [
// { status: 'fulfilled', value: '接口1成功' },
// { status: 'rejected', reason: '接口2失败' }
// ]
});
2.1.3 Promise.race()
接收一个 Promise 数组作为参数,返回一个新的 Promise,哪个 Promise 先改变状态,新 Promise 就跟随哪个状态(无论成功还是失败)。
适用场景:设置异步操作的超时时间(比如接口请求超过5秒就提示超时)。
// 模拟接口请求(3秒返回)
const request = new Promise((resolve) => setTimeout(() => resolve("接口请求成功"), 3000));
// 模拟超时(2秒后失败)
const timeout = new Promise((reject) => setTimeout(() => reject(new Error("请求超时")), 2000));
Promise.race([request, timeout])
.then((result) => {
console.log("成功:", result);
})
.catch((error) => {
console.log("失败:", error.message); // 2秒后输出:请求超时
});
2.1.4 Promise.resolve() 和 Promise.reject()
两个快捷方法,用于快速创建一个已确定状态的 Promise:
- Promise.resolve(value):创建一个 fulfilled 状态的 Promise,结果为 value。
- Promise.reject(error):创建一个 rejected 状态的 Promise,错误信息为 error。
// 等价于 new Promise(resolve => resolve("成功"))
Promise.resolve("成功").then((result) => console.log(result));
// 等价于 new Promise((resolve, reject) => reject(new Error("失败")))
Promise.reject(new Error("失败")).catch((error) => console.log(error.message));
2.2 Promise 链式调用的核心逻辑
then 方法的返回值决定了下一个 then 的参数,这是链式调用的核心,分三种情况:
- 如果 then 回调返回普通值,下一个 then 会接收这个普通值作为参数,状态为 fulfilled。
- 如果 then 回调返回一个 Promise 实例,下一个 then 会等待这个 Promise 状态改变,接收其结果。
- 如果 then 回调抛出错误,下一个 catch 会捕获这个错误,状态为 rejected。
Promise.resolve(1)
.then((num) => {
return num + 1; // 返回普通值
})
.then((num) => {
return new Promise((resolve) => resolve(num + 1)); // 返回 Promise
})
.then((num) => {
throw new Error(`数字超过3:${num}`); // 抛出错误
})
.catch((error) => {
console.log(error.message); // 输出:数字超过3:3
});
2.3 Promise 与回调地狱的对比
传统回调函数嵌套(回调地狱):代码层级深、可读性差、难以维护。
// 回调地狱示例(模拟三次异步操作,依次依赖)
setTimeout(() => {
console.log("第一步");
setTimeout(() => {
console.log("第二步");
setTimeout(() => {
console.log("第三步");
}, 1000);
}, 1000);
}, 1000);
用 Promise 链式调用优化后:代码层级扁平、逻辑清晰、易维护。
// Promise 优化回调地狱
new Promise((resolve) => {
setTimeout(() => {
console.log("第一步");
resolve();
}, 1000);
})
.then(() => {
return new Promise((resolve) => {
setTimeout(() => {
console.log("第二步");
resolve();
}, 1000);
});
})
.then(() => {
setTimeout(() => {
console.log("第三步");
}, 1000);
});
三、场景模拟
Promise 是前端异步开发的基础,以下是实际项目中最常用的几个场景,结合案例说明如何应用。
场景1:接口请求(最核心场景)
前端与后端交互的接口请求(如 axios、fetch),本质上都是基于 Promise 实现的,我们可以直接使用 then/catch 处理请求结果和错误。
// 示例:使用 axios 发送请求(axios 本身返回 Promise)
import axios from "axios";
// 1. 单个接口请求
axios.get("/api/userinfo")
.then((response) => {
console.log("用户信息:", response.data);
// 渲染页面
})
.catch((error) => {
console.log("请求失败:", error.message);
// 提示用户错误(如“网络异常,请重试”)
})
.finally(() => {
// 关闭加载动画
document.getElementById("loading").style.display = "none";
});
// 2. 多个接口并行请求(需全部成功)
Promise.all([
axios.get("/api/userinfo"),
axios.get("/api/orderlist"),
axios.get("/api/cart")
])
.then(([userRes, orderRes, cartRes]) => {
// 解构获取三个接口的返回数据
const userInfo = userRes.data;
const orderList = orderRes.data;
const cart = cartRes.data;
// 同时渲染多个模块的数据
})
.catch((error) => {
console.log("某个接口请求失败:", error);
});
场景2:设置请求超时(避免接口长期无响应)
结合 Promise.race(),实现接口请求超时提示,提升用户体验。
// 封装一个带超时的请求函数
function requestWithTimeout(url, timeout = 5000) {
// 接口请求
const request = axios.get(url);
// 超时提示
const timeoutPromise = new Promise((reject) => {
setTimeout(() => {
reject(new Error("请求超时,请检查网络后重试"));
}, timeout);
});
// 谁先完成就返回谁
return Promise.race([request, timeoutPromise]);
}
// 使用
requestWithTimeout("/api/userinfo")
.then((response) => {
console.log("请求成功:", response.data);
})
.catch((error) => {
console.log("请求失败:", error.message);
// 弹窗提示用户
});
场景3:异步操作的顺序执行(依赖关系)
当多个异步操作有依赖关系(比如第二个操作需要第一个操作的结果),使用 Promise 链式调用实现顺序执行。
// 场景:先获取用户ID,再根据ID获取用户详情
axios.get("/api/userid")
.then((response) => {
const userId = response.data.userId;
// 返回一个新的请求,下一个then会等待其完成
return axios.get(`/api/userdetail?userId=${userId}`);
})
.then((response) => {
console.log("用户详情:", response.data);
// 渲染用户详情页面
})
.catch((error) => {
console.log("请求失败:", error.message);
});
场景4:批量操作的结果统计
结合 Promise.allSettled(),批量处理异步操作,统计成功和失败的数量(比如批量删除、批量提交)。
// 场景:批量删除多个订单(每个删除操作是一个接口请求)
const orderIds = [101, 102, 103];
// 生成多个删除请求的Promise数组
const deletePromises = orderIds.map(id => {
return axios.delete(`/api/order/${id}`);
});
// 统计所有删除结果
Promise.allSettled(deletePromises)
.then((results) => {
// 筛选成功和失败的数量
const successCount = results.filter(item => item.status === "fulfilled").length;
const failCount = results.filter(item => item.status === "rejected").length;
console.log(`批量删除完成:成功${successCount}个,失败${failCount}个`);
// 提示用户结果
});
场景5:图片加载完成后执行操作
图片加载是异步操作,使用 Promise 监听图片加载完成/失败,再执行后续逻辑(比如图片加载完成后显示,失败则显示占位图)。
// 封装图片加载函数
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
// 图片加载完成
img.onload = () => resolve(img);
// 图片加载失败
img.onerror = () => reject(new Error(`图片加载失败:${src}`));
// 设置图片地址(触发加载)
img.src = src;
});
}
// 使用
loadImage("https://example.com/avatar.jpg")
.then((img) => {
// 图片加载完成,插入到页面
document.body.appendChild(img);
})
.catch((error) => {
console.log(error.message);
// 显示占位图
const placeholder = document.createElement("img");
placeholder.src = "https://example.com/placeholder.jpg";
document.body.appendChild(placeholder);
});
复习OK