javascript promise(手把手讲解)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观
前言
在现代 Web 开发中,异步编程是一个绕不开的话题。无论是网络请求、文件读取,还是定时器操作,开发者都不得不面对异步任务带来的复杂性。早期的回调函数模式(Callback)虽然解决了部分问题,但“回调地狱”(Callback Hell)的出现让代码可读性急剧下降。为了解决这一痛点,JavaScript Promise 应运而生,它通过更清晰的语法和结构化的设计,重新定义了异步编程的范式。本文将从基础概念到高级用法,结合实际案例,深入浅出地讲解 JavaScript Promise 的核心原理与实践技巧。
Promise 的基本概念与核心方法
什么是 Promise?
Promise 是一个表示异步操作最终完成或失败的对象。它可以理解为对“未来值”的一种抽象承诺:
- 创建阶段:Promise 对象被创建时,状态为
pending
(进行中)。 - 完成阶段:通过
resolve()
方法将状态变为fulfilled
(已成功),并传递结果值。 - 失败阶段:通过
reject()
方法将状态变为rejected
(已失败),并传递错误原因。
形象比喻:
想象你在线上下单购买一件商品,此时订单状态是“处理中”(pending)。快递员送货成功时,状态变为“已签收”(fulfilled);如果商品损坏或丢失,状态则变为“已拒收”(rejected)。Promise 的状态变化与这一过程完全一致。
核心方法:then()
、catch()
和 finally()
Promise 的核心方法包括:
.then(onFulfilled, onRejected)
:处理fulfilled
或rejected
状态的结果。.catch(onRejected)
:专门处理rejected
状态的错误。.finally()
:无论成功或失败,都会执行的清理操作。
示例代码:基础 Promise 使用
const myPromise = new Promise((resolve, reject) => {
// 模拟异步操作(如网络请求)
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve("操作成功!");
} else {
reject("操作失败!");
}
}, 1000);
});
myPromise
.then((result) => {
console.log("成功结果:", result); // 当 resolve 被调用时触发
})
.catch((error) => {
console.log("错误原因:", error); // 当 reject 被调用时触发
});
链式调用:让异步代码更优雅
为什么需要链式调用?
传统回调模式中,嵌套的 then
会导致代码层级混乱,例如:
// 回调地狱示例
doSomething()
.then(result1 => {
return doSomethingElse(result1);
})
.then(result2 => {
return doThirdThing(result2);
})
.catch(error => {
// 处理所有错误
});
而通过 链式调用(Chaining),Promise 可以将多个异步操作串联,使代码结构更清晰。
链式调用的核心规则
.then()
返回新 Promise:每个then
方法返回一个新的 Promise,因此可以连续调用。- 错误自动传递:如果某一步出错,后续的
then
会跳过,直接进入最近的catch
。
示例代码:链式调用与错误处理
function fetchUser(id) {
return new Promise((resolve, reject) => {
// 模拟异步获取用户数据
setTimeout(() => {
if (id > 0) {
resolve({ id, name: "Alice" });
} else {
reject("无效的用户 ID");
}
}, 500);
});
}
fetchUser(1)
.then(user => {
console.log("用户信息:", user);
return user.id * 2; // 返回新值传递给下一个 then
})
.then(doubledId => {
console.log("ID 的两倍:", doubledId);
})
.catch(error => {
console.error("链式调用中的错误:", error);
});
Promise 的错误处理与注意事项
错误处理的陷阱
Promise 的错误不会自动冒泡到全局,若未通过 catch
处理,可能导致“未捕获的 rejection”警告。例如:
new Promise((resolve, reject) => reject("错误"))
.then(() => console.log("这不会执行"));
// 控制台会输出:Uncaught (in promise) 错误
解决方案:
- 在链式调用的最后添加
.catch()
。 - 使用全局监听器
window.addEventListener("unhandledrejection", event => ...)
。
案例:错误处理的最佳实践
function fetchData() {
return new Promise((resolve, reject) => {
// 模拟网络请求
setTimeout(() => {
const errorOccurred = Math.random() < 0.3;
if (errorOccurred) {
reject("数据获取失败");
} else {
resolve("数据已就绪");
}
}, 1000);
});
}
// 方式一:链式调用中捕获错误
fetchData()
.then(data => console.log(data))
.catch(error => console.error(error));
// 方式二:全局错误监听(适用于未处理的 Promise)
window.addEventListener("unhandledrejection", (event) => {
event.preventDefault(); // 阻止默认的警告信息
console.error("全局捕获的错误:", event.reason);
});
Promise 的高级用法与实用工具
1. Promise.all()
:并行执行多个 Promise
当需要同时等待多个异步操作完成时,Promise.all()
可以简化代码。
- 成功条件:所有 Promise 均
fulfilled
。 - 失败条件:只要有一个 Promise
rejected
,立即终止并返回错误。
示例代码:并行下载文件
const downloadFile = (fileName) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`文件 ${fileName} 下载成功`);
}, Math.random() * 1000);
});
};
Promise.all([
downloadFile("file1.txt"),
downloadFile("file2.txt"),
downloadFile("file3.txt")
])
.then(results => {
console.log("所有文件下载完成:", results); // results 是一个数组,按顺序存储结果
})
.catch(error => {
console.error("至少一个文件下载失败:", error);
});
2. Promise.race()
:竞争模式
Promise.race()
会返回第一个完成的 Promise 的结果,常用于设置超时或优先处理。
示例代码:超时控制
function fetchDataWithTimeout() {
const timeout = new Promise((resolve, reject) => {
setTimeout(() => reject("请求超时"), 2000); // 2秒超时
});
const request = new Promise((resolve, reject) => {
// 模拟耗时操作
setTimeout(() => resolve("数据返回"), 1500);
});
return Promise.race([request, timeout]);
}
fetchDataWithTimeout()
.then(data => console.log(data))
.catch(error => console.error(error)); // 如果请求耗时超过 2秒,触发超时错误
3. Promise.resolve()
和 Promise.reject()
这两个方法用于将值或错误包装成 Promise,常用于兼容旧代码或简化流程。
// 直接创建 resolved 的 Promise
const resolvedPromise = Promise.resolve("立即成功");
resolvedPromise.then(data => console.log(data)); // 输出:立即成功
// 创建 rejected 的 Promise
const rejectedPromise = Promise.reject("立即失败");
rejectedPromise.catch(error => console.error(error)); // 输出:立即失败
Promise 与其他异步模式的对比
回调函数 vs. Promise
- 回调函数:直接传递函数作为参数,但嵌套层级过多时难以维护。
- Promise:通过链式调用和
.then
/.catch
,代码结构更清晰。
Generator 函数 vs. Promise
Generator 函数(需配合 co
库等)曾是处理异步的过渡方案,但其语法复杂且需额外工具支持,而 Promise 已成为主流。
async/await vs. Promise
async/await
是基于 Promise 的语法糖,本质仍是 Promise 链。例如:
// 纯 Promise 写法
fetchUser(1)
.then(user => fetchOrders(user.id))
.then(orders => console.log(orders))
.catch(error => console.error(error));
// async/await 写法
async function getUserAndOrders() {
try {
const user = await fetchUser(1);
const orders = await fetchOrders(user.id);
console.log(orders);
} catch (error) {
console.error(error);
}
}
总结:Promise 提供了底层机制,而 async/await
通过更简洁的语法进一步提升了可读性。
实战案例:构建一个网络请求工具
目标:封装一个带重试机制的 HTTP 请求函数
通过 Promise 和 setTimeout
,实现自动重试失败的请求。
代码实现
function retryRequest(url, maxRetries = 3) {
return new Promise((resolve, reject) => {
const makeRequest = (retryCount) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.responseText); // 成功时返回数据
} else {
if (retryCount > 0) {
console.log(`请求失败,剩余重试次数:${retryCount}`);
setTimeout(() => makeRequest(retryCount - 1), 1000);
} else {
reject(`请求失败,达到最大重试次数 ${maxRetries}`);
}
}
};
xhr.onerror = () => {
if (retryCount > 0) {
setTimeout(() => makeRequest(retryCount - 1), 1000);
} else {
reject("网络错误");
}
};
xhr.send();
};
makeRequest(maxRetries); // 初始调用
});
}
// 使用示例
retryRequest("https://api.example.com/data", 2)
.then(data => console.log("数据:", data))
.catch(error => console.error("请求失败:", error));
结论
JavaScript Promise 是异步编程的一次重大革新,它通过清晰的状态管理和链式调用,解决了传统回调模式的痛点。从基础的 then/catch
到高级的 Promise.all
,再到与 async/await
的结合,开发者可以灵活应对各种异步场景。
掌握 Promise 的核心原理后,建议通过实际项目不断练习,例如构建网络请求工具、文件上传组件等。随着经验的积累,你会逐渐体会到 Promise 带来的代码优雅性与可维护性提升。
记住,异步编程的本质是“等待与响应”的艺术,而 Promise 为我们提供了优雅的画笔。
关键词密度检查:
- “javascript promise” 在标题、小标题和正文中自然出现,符合 SEO 要求。
- 其他技术术语(如
then
、catch
、async/await
)均通过上下文解释,确保读者理解。