JavaScript Promise 终极指南 解决回调地狱的异步神器 99% 开发者都在用
你有没有写过这种让人头大的代码:想先获取用户信息,再拿用户的订单列表,最后查订单里的商品详情,结果嵌套了三层回调函数,代码乱得像一团毛线,改个逻辑得从头捋到尾,稍微多嵌套一层就成了 “回调地狱”?
别愁!今天给你拆透 JavaScript 里的 “异步救星”——Promise,小索奇当年第一次用它重构回调代码时,简直像解开了缠了半年的耳机线,清爽到想哭!
简单说就是,Promise 是专门管异步操作的 “容器”,它能把那些需要等的操作(比如接口请求、文件读取)包起来,让代码不用嵌套着写,而是像 “排队办事” 一样清晰。说白了,就是把 “先干 A 再干 B,B 干完再干 C” 的逻辑,从 “套娃” 变成 “流水线”。
先给你看个对比,以前用回调写的代码可能是这样的:
// 回调地狱现场
getUserInfo (userId, function (user) {
getOrderList (user.id, function (orders) {
getProductDetail (orders [0].productId, function (product) {
console.log (product); // 三层嵌套,眼睛都花了
}, function (err) {
console.error (' 查商品错了 ', err);
});
}, function (err) {
console.error (' 查订单错了 ', err);
});
}, function (err) {
console.error (' 查用户错了 ', err);
});
这代码要是再加一层操作,是不是想骂街?换成 Promise 写法,瞬间豁然开朗:
// Promise 写法
getUserInfo (userId)
.then (user => getOrderList (user.id))
.then (orders => getProductDetail (orders [0].productId))
.then (product => console.log (product))
.catch (err => console.error (' 出错了 ', err));
是不是清爽到飞起?这里的 then 方法就是 “上一步干完干这个”,catch 则是 “任何一步出错都走这”,不用每一步都写错误处理,简直是解放双手!
这背后的原因是啥呢?因为 Promise 有三种状态:pending(进行中)、fulfilled(成功)、rejected(失败)。一旦状态变了,就再也改不了 —— 比如从 pending 变成 fulfilled,就永远是成功状态了。这就保证了异步操作的结果只会被处理一次,不会出现 “又成功又失败” 的混乱情况。
说到这儿可能有人会问:“我自己写的函数怎么改成返回 Promise 的?” 其实超简单,只要把异步操作包进 Promise 里就行:
// 把普通回调函数改成 Promise 版本
function getUserInfo (userId) {
return new Promise ((resolve, reject) => {
// 模拟接口请求
setTimeout (() => {
if (userId) {
resolve ({ id: userId, name: ' 张三 ' }); // 成功就调用 resolve
} else {
reject (new Error (' 用户 ID 不能为空 ')); // 失败就调用 reject
}
}, 1000);
});
}
看到没?resolve 就是 “告诉 Promise 干成了,把结果传出去”,reject 是 “干砸了,把错误传出去”。这样改造完,就能用 then 和 catch 链式调用了,是不是很简单?
不过 Promise 也有坑,小索奇亲测踩过好几次!比如新手容易犯的 “忘记写 catch”,结果异步操作出错了,控制台一片红却找不到原因 —— 因为 Promise 的错误不会主动抛出来,必须用 catch 捕获,或者在 then 的第二个参数里处理。
还有个更隐蔽的坑:在 Promise 里写了同步代码报错,比如这样:
new Promise ((resolve, reject) => {
console.log (a); //a 没定义,同步报错
resolve (' 成功 ');
}).catch (err => console.log (err));
这时候 catch 能抓到错误吗?答案是能!因为 Promise 会把 executor 函数(就是 new Promise 里的回调)里的同步错误自动变成 reject 状态,这点倒是很贴心,但要是不注意,也容易误以为是异步操作出了问题。
再给你说个进阶技巧:Promise.all 和 Promise.race 的用法。如果要同时请求三个接口,等全部返回再处理,就用 Promise.all:
Promise.all ([
getUserInfo (1),
getOrderList (1),
getProductDetail (101)
]).then (results => {
const user = results [0];
const orders = results [1];
const product = results [2];
// 三个结果都拿到了,开始处理
}).catch (err => {
// 只要有一个接口失败,就会进这里
});
要是想 “谁先返回就用谁”,比如同时请求两个图片接口,哪个快显示哪个,就用 Promise.race:
Promise.race ([
fetchImage ('url1'),
fetchImage ('url2')
]).then (image => {
console.log (' 显示快的图片 ', image);
});
是不是超实用?你平时处理多异步操作的时候,是用嵌套回调还是 Promise?评论区说说你第一次用 Promise 踩过的坑!
前面忘了提一句,Promise 虽然解决了回调地狱,但如果逻辑复杂,一堆 then 连起来也会有点乱,这时候就可以用 async/await 了 —— 不过那是 Promise 的 “语法糖”,本质还是基于 Promise 实现的,先把 Promise 吃透,再学 async/await 会更轻松。
我是【即兴小索奇】,点击关注,后台回复 领取,获取更多相关资源