ES6新特性之Promise:告别“回调地狱“,拥抱优雅异步编程
Promise 是 JavaScript 异步编程的基础,后续的 `async/await` 也是基于 Promise 实现的。掌握了 Promise,就能轻松应对项目中的各种异步场景(调接口、文件上传、定时器等)~
在 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→fulfilled或pending→rejected),就不能再变; then和catch会等待异步操作完成后再执行,不用手动监听。
四、用 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);
});
封装的好处:
- 复用性强:所有请求都能调用这个方法,不用重复写
$.ajax和Promise; - 维护方便:如果要改请求配置(比如加请求头、超时时间),只改一处;
- 错误统一:所有请求的错误都能通过 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,就能轻松应对项目中的各种异步场景(调接口、文件上传、定时器等)~
更多推荐



所有评论(0)