在 JavaScript 世界里,有个绕不开的 “特性”——单线程执行。简单说就是代码只能 “排队” 一行一行跑,不能同时处理多个任务。但像网络请求(比如调接口)、浏览器事件(比如点击按钮)这些操作,总不能让代码一直等着吧?这时候就需要 “异步执行”—— 让这些耗时操作后台跑,跑完再通知代码处理结果。
而异步操作最开始的实现方式,就是回调函数。可一旦遇到 “多个异步操作依赖前一个结果” 的场景(比如先登录、再查课程、最后查成绩),回调函数就会层层嵌套,变成让人头疼的 “回调地狱”。今天就用 Promise 解决这个问题 ~

一、先感受下:回调地狱有多 “折磨人”?

先看一个真实场景:用户登录后,查询他的课程,再根据课程查成绩。需要调用 3 个接口(用 JSON 文件模拟),且后一个接口依赖前一个的结果。

用回调函数实现:嵌套到 “窒息”

// 模拟数据文件
// user.json
const userData = { id: 1, name: "张三", password: "123456" };

// user_course_1.json
const courseData = { id: 10, name: "语文" };

// course_score_10.json
const scoreData = { id: 100, score: 90 };

// 传统回调方式(回调地狱)
function getUserWithCallback(callback) {
  setTimeout(() => {
    console.log("查询用户:", userData);
    callback(userData.id);
  }, 1000);
}

function getCourseWithCallback(userId, callback) {
  setTimeout(() => {
    console.log("查询课程:", courseData);
    callback(courseData.id);
  }, 1000);
}

function getScoreWithCallback(courseId, callback) {
  setTimeout(() => {
    console.log("查询分数:", scoreData);
    callback(scoreData.score);
  }, 1000);
}

// 回调地狱预警⚠️ 层层嵌套像金字塔
getUserWithCallback(userId => {
  getCourseWithCallback(userId, courseId => {
    getScoreWithCallback(courseId, score => {
      console.log("最终分数:", score);
    });
  });
});

回调地狱的 3 大痛点:

  • 代码臃肿:嵌套层级越多,代码越靠右边,像 “缩进黑洞”;
  • 维护困难:想改中间某个步骤,得层层找对应的嵌套块;
  • 错误难处理:每个回调都要写单独的 error,重复且容易遗漏。

这时候,Promise 就该登场了 —— 它的核心作用就是:把 “嵌套的回调” 改成 “平铺的链式调用”,让异步代码更清爽、更好维护。

// 实际示例:模拟异步获取数据
getUser(userId)
  .then(user => getCourses(user.id))
  .then(courses => getScores(courses[0].id))
  .then(score => getRank(score.id))
  .then(rank => console.log("最终结果:", rank))
  .catch(error => console.error("出错了:", error));

二、Promise 是什么?一句话看懂

Promise 是 JavaScript 中专门处理异步操作的对象,它能把异步操作的 “结果”(成功 / 失败)封装起来,用统一的方式处理,避免嵌套。

Promise的三种状态
Promise就像一个状态机,有三种状态:

// 1. pending(等待中) - 初始状态
// 2. fulfilled(已成功) - 操作成功完成
// 3. rejected(已失败) - 操作失败

// 状态变化不可逆:pending → fulfilled 或 pending → rejected

可以把 Promise 想象成一个 “异步任务的容器”:

  • 你把异步操作(比如调接口)放进容器里;
  • 容器会自动跟踪任务状态:“pending(进行中)”→“fulfilled(成功)” 或 “rejected(失败)”;
  • 任务结束后,你不用嵌套回调,而是用 then (处理成功)和 catch (处理失败)来接收结果。

三、Promise 基础语法:3 步上手

1. 创建 Promise 对象

new Promise() 创建,里面传入一个函数(叫 “执行器函数”),函数有两个参数:

  • resolve:异步操作成功时调用,把成功的结果传出去;
  • reject:异步操作失败时调用,把错误信息传出去。
// 基本语法(箭头函数简写)
const promise = new Promise((resolve, reject) => {
  // 在这里执行异步操作
  
  // 成功时调用 resolve(结果)
  // 失败时调用 reject(错误)
});

function getUserData(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (userId > 0) {
        resolve({ id: userId, name: "小明", age: 18 });
      } else {
        reject(new Error("用户ID无效"));
      }
    }, 1000); // 模拟1秒延迟
  });
}

2. 处理结果:then + catch

promise.then() 接收成功的结果,promise.catch() 接收失败的错误,链式调用更清爽:

const promise = getUserData(1);

// 方式1:then + catch(推荐)
promise
  .then(user => {
    console.log("获取用户成功:", user);
    return user.age; // 可以返回新值
  })
  .then(age => {
    console.log("用户年龄:", age);
  })
  .catch(error => {
    console.error("获取用户失败:", error);
  })
  .finally(() => {
    console.log("无论成功失败都会执行");
  });

  
  // 方式2:then接收两个回调函数
  promise.then(
	  user => console.log("成功:", user),  // 成功回调
	  error => console.error("失败:", error) // 失败回调
	);

核心特点:

  • 状态一旦改变(pending→fulfilledpending→rejected),就不能再变;
  • thencatch等待异步操作完成后再执行,不用手动监听。

四、用 Promise 改造回调地狱:平铺代替嵌套

回到 “登录→查课程→查成绩” 的案例,用 Promise 改造后,代码直接 “拉直”,再也没有嵌套!

// 封装为Promise版本的函数
function getUser() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("查询用户:", userData);
      resolve(userData.id);
    }, 1000);
  });
}

function getCourse(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("查询课程:", courseData);
      resolve(courseData.id);
    }, 1000);
  });
}

function getScore(courseId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("查询分数:", scoreData);
      resolve(scoreData.score);
    }, 1000);
  });
}

// Promise链式调用(优雅!)
getUser()
  .then(userId => getCourse(userId))
  .then(courseId => getScore(courseId))
  .then(score => console.log("最终分数:", score))
  .catch(error => console.error("出错:", error));

对比回调地狱:

  • 原来的 “三层嵌套” 变成了 “三层链式调用”,代码平铺,一眼能看清执行顺序;
  • 错误处理统一用 catch,不用重复写 error 回调;
  • 数据传递更清晰:上一步的结果通过 resolve 传给下一步的 then。

五、企业级优化:封装通用请求方法

实际开发中,我们不会每次都写 new Promise,而是封装成通用的请求函数(比如 get、post),复用代码。

封装通用请求方法

// 模拟ajax请求(实际项目中使用axios或fetch)
function ajax(url) {
  return new Promise((resolve, reject) => {
    console.log(`请求:${url}`);
    setTimeout(() => {
      // 模拟不同URL返回不同数据
      if (url.includes("user.json")) {
        resolve({ id: 1, name: "张三" });
      } else if (url.includes("user_course")) {
        resolve({ id: 10, name: "语文" });
      } else if (url.includes("course_score")) {
        resolve({ id: 100, score: 90 });
      } else {
        reject(new Error("请求失败"));
      }
    }, 1000);
  });
}

用封装后的方法实现需求:代码更简洁

// 登录→查课程→查成绩:链式调用一步到位
// 使用封装的方法
ajax("mock/user.json")
  .then(user => {
    console.log("用户信息:", user);
    return ajax(`mock/user_course_${user.id}.json`);
  })
  .then(course => {
    console.log("课程信息:", course);
    return ajax(`mock/course_score_${course.id}.json`);
  })
  .then(score => {
    console.log("分数信息:", score);
  })
  .catch(error => {
    console.error("请求出错:", error);
  });

封装的好处:

  • 复用性强:所有请求都能调用这个方法,不用重复写 $.ajaxPromise
  • 维护方便:如果要改请求配置(比如加请求头、超时时间),只改一处;
  • 错误统一:所有请求的错误都能通过 catch 集中处理,比如弹提示框、上报错误。

六、Promise高级特性

1. Promise链

每个then()都返回一个新的Promise,可以继续链式调用:

getUser()
  .then(user => {
    console.log("第1步:", user);
    return user.id * 10; // 返回普通值
  })
  .then(num => {
    console.log("第2步:", num);
    return getCourse(num); // 返回新的Promise
  })
  .then(course => {
    console.log("第3步:", course);
    // 如果没有return,默认返回undefined
  })
  .then(result => {
    console.log("第4步:", result); // undefined
  });

2. 错误处理

// 方式1:catch捕获所有错误
getUser()
  .then(user => getCourse(user.id))
  .then(course => getScore(course.id))
  .catch(error => {
    console.error("链中任何错误都会到这里:", error);
    return { score: 0 }; // 可以提供默认值
  })
  .then(score => {
    console.log("分数(含默认值):", score);
  });

// 方式2:每个then都可以单独处理错误
getUser()
  .then(
    user => getCourse(user.id),
    error => {
      console.error("获取用户失败:", error);
      throw error; // 继续抛出,让后面的catch捕获
    }
  )
  .then(course => getScore(course.id))
  .catch(error => console.error("最终错误:", error));

3. Promise静态方法

// 1. Promise.resolve() - 创建已成功的Promise
Promise.resolve("直接成功").then(value => console.log(value));

// 2. Promise.reject() - 创建已失败的Promise
Promise.reject(new Error("直接失败")).catch(error => console.error(error));

// 3. Promise.all() - 等待所有Promise完成
const promise1 = getUser();
const promise2 = getCourse(1);
const promise3 = getScore(10);

Promise.all([promise1, promise2, promise3])
  .then(([user, course, score]) => {
    console.log("全部完成:", user, course, score);
  })
  .catch(error => {
    console.error("有一个失败就全部失败:", error);
  });

// 4. Promise.race() - 竞速,第一个完成的胜出
Promise.race([
  getUser(),
  new Promise((_, reject) => setTimeout(() => reject("超时"), 500))
])
  .then(user => console.log("先获取到用户:", user))
  .catch(error => console.error("超时或失败:", error));

// 5. Promise.allSettled() - 等待所有Promise完成(无论成功失败)
Promise.allSettled([promise1, promise2, promise3])
  .then(results => {
    results.forEach(result => {
      if (result.status === "fulfilled") {
        console.log("成功:", result.value);
      } else {
        console.log("失败:", result.reason);
      }
    });
  });

// 6. Promise.any() - 第一个成功的胜出
Promise.any([
  Promise.reject("错误1"),
  Promise.resolve("成功1"),
  Promise.resolve("成功2")
])
  .then(value => console.log("第一个成功的:", value));

七、实际应用场景

场景1:并行请求

// 需要同时获取用户信息、订单信息和商品信息
function fetchUser() {
  return ajax("/api/user");
}

function fetchOrders() {
  return ajax("/api/orders");
}

function fetchProducts() {
  return ajax("/api/products");
}

// 并行执行,提高效率
Promise.all([fetchUser(), fetchOrders(), fetchProducts()])
  .then(([user, orders, products]) => {
    console.log("所有数据加载完成");
    renderPage(user, orders, products);
  })
  .catch(error => {
    console.error("加载失败:", error);
    showErrorPage();
  });

场景2:请求超时控制

function fetchWithTimeout(url, timeout = 5000) {
  return Promise.race([
    ajax(url),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error("请求超时")), timeout)
    )
  ]);
}

fetchWithTimeout("/api/data", 3000)
  .then(data => console.log("数据:", data))
  .catch(error => console.error("错误:", error));

场景3:顺序执行但有依赖

// 注册流程:验证手机号 → 发送验证码 → 创建用户 → 登录
function verifyPhone(phone) {
  return ajax("/api/verify-phone", { phone });
}

function sendSMSCode(phone) {
  return ajax("/api/send-sms", { phone });
}

function createUser(phone, code) {
  return ajax("/api/create-user", { phone, code });
}

function login(phone) {
  return ajax("/api/login", { phone });
}

// 链式调用,清晰表达依赖关系
verifyPhone("13800138000")
  .then(() => sendSMSCode("13800138000"))
  .then(code => createUser("13800138000", code))
  .then(() => login("13800138000"))
  .then(user => {
    console.log("注册并登录成功:", user);
    redirectToHome();
  })
  .catch(error => {
    console.error("注册失败:", error);
    showErrorMessage(error.message);
  });

八、常见陷阱与最佳实践

陷阱1:忘记返回Promise

// ❌ 错误写法
getUser()
  .then(user => {
    getCourse(user.id); // 忘记return!
  })
  .then(course => {
    // course是undefined!
    console.log(course);
  });

// ✅ 正确写法
getUser()
  .then(user => {
    return getCourse(user.id); // 记得return!
  })
  .then(course => {
    console.log(course); // 正常获取到课程
  });

陷阱2:错误处理不当

// ❌ 错误写法 - catch放错位置
getUser()
  .then(user => {
    return getCourse(user.id).catch(error => {
      console.error("课程获取失败");
    });
  })
  .then(course => {
    // 如果getCourse失败,这里course是undefined
    getScore(course.id); // 可能报错!
  });

// ✅ 正确写法 - 统一错误处理
getUser()
  .then(user => getCourse(user.id))
  .then(course => getScore(course.id))
  .catch(error => {
    console.error("任何步骤失败都会到这里");
    return { score: 0 }; // 提供默认值
  });

最佳实践

// 1. 总是返回Promise链
function getUserInfo(userId) {
  return getUser(userId)
    .then(user => getCourse(user.id))
    .then(course => getScore(course.id));
}

// 2. 使用async/await(ES7语法糖,更易读)
async function getUserInfoAsync(userId) {
  try {
    const user = await getUser(userId);
    const course = await getCourse(user.id);
    const score = await getScore(course.id);
    return score;
  } catch (error) {
    console.error("获取用户信息失败:", error);
    throw error;
  }
}

// 3. 合理使用Promise.all并行处理
async function loadDashboard() {
  const [user, notifications, messages] = await Promise.all([
    getUser(),
    getNotifications(),
    getMessages()
  ]);
  return { user, notifications, messages };
}

// 4. 添加超时处理
function withTimeout(promise, timeout, timeoutMessage = "操作超时") {
  return Promise.race([
    promise,
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error(timeoutMessage)), timeout)
    )
  ]);
}

总结:Promise 核心价值

  • 解决回调地狱:把嵌套的异步代码改成平铺的链式调用,代码更优雅、易维护;
  • 统一异步处理:用 then 处理成功、catch 处理错误,逻辑更清晰;
  • 状态可追踪:Promise 对象的状态(进行中 / 成功 / 失败)一旦确定,就不会改变,避免重复处理。

Promise 是 JavaScript 异步编程的基础,后续的 async/await 也是基于 Promise 实现的。掌握了 Promise,就能轻松应对项目中的各种异步场景(调接口、文件上传、定时器等)~

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐