从零开始 捡起知识点(y)

0 阅读6分钟

一、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 的参数,这是链式调用的核心,分三种情况:

  1. 如果 then 回调返回普通值,下一个 then 会接收这个普通值作为参数,状态为 fulfilled。
  2. 如果 then 回调返回一个 Promise 实例,下一个 then 会等待这个 Promise 状态改变,接收其结果。
  3. 如果 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