大家好,我是第八哥,专注前端开发10年的老司机。JavaScript
异步编程这事儿,真是每个前端开发者都绕不开的坎。
从最开始被回调函数折磨得头疼,到后来用Promise
理顺逻辑,再到现在async/await
信手拈来,这一路的进化,全是踩坑踩出来的经验。
今天就用大白话,带大家捋捋这事儿,再配上实例代码,保证看完就懂。
一、回调函数:异步的起点,也是 “地狱” 的开端
最早的时候,JavaScript处理异步就靠回调函数。比如加载图片、发送Ajax请求等,都得靠它。
啥是回调?简单地说就是 “等我做完这事儿,再回头调用你给的函数”。比如这样:
function loadData(callback) {
setTimeout(() => {
const data = ' 加载完成 ';
callback(data);
}, 1000);
}
loadData((result) => {
console.log(result); // 1 秒后输出 “加载完成”
});
看起来挺简单吧?但是如果遇到多个异步操作要按顺序来执行的话,那么麻烦就来了。
比如先加载用户信息,然后加载用户订单,最后再加载订单详情,代码就会变成这样:
loadUser(userId, (user) => {
loadOrders(user.id, (orders) => {
loadOrderDetail(orders[0].id, (detail) => {
// 三层嵌套,还能看
});
});
});
这还只是三层嵌套,如果再多几层,代码就会像金字塔一样往右偏,这就是传说中的 “回调地狱”。
这样的代码,维护起来简直想原地辞职。哪怕改一个参数,都得从里往外扒三层,这谁顶得住啊。
二、Promise:给异步一个 “承诺”,告别嵌套
大概在2015年左右,Promise
来了,算是给我们松了口气。它就像给异步操作发了个 “承诺书”:成了就告诉我,不成也告诉我。
Promise有三种状态:pending(进行中)、fulfilled(成功)、rejected(失败)。状态一旦变了,就改不回去了。
用Promise改写上面的加载逻辑,代码是这样的:
function loadUser(userId) {
return new Promise((resolve) => {
setTimeout(() => resolve({ id: userId }), 1000);
});
}
function loadOrders(userId) {
return new Promise((resolve) => {
setTimeout(() => resolve([{ id: 'order1' }]), 1000);
});
}
loadUser(1)
.then(user => loadOrders(user.id))
.then(orders => loadOrderDetail(orders[0].id))
.then(detail => console.log(detail))
.catch(err => console.log(' 出错了:', err));
看到没?用.then ()
链式调用,代码一下子变平了,不再往右偏了。就算加再多异步步骤,也是往下加.then(),清爽多了。
而且 Promise 还能并行处理异步操作,比如 Promise.all()
,同时加载多个资源,效率翻倍:
Promise.all([loadUser(1), loadGoods()])
.then(([user, goods]) => {
console.log(' 用户和商品都加载完了 ');
});
三、async/await:让异步代码,看起来像同步
Promise 虽然解决了嵌套问题,但链式调用多了,还是有点啰嗦。2017年,async/await
横空出世,直接把异步代码写成了同步的样子。
它其实是 Promise 的语法糖,但甜到让人上瘾。用法特简单:在函数前加 async,里面用 await 等待 Promise 执行完成。
还是刚才的例子,用 async/await 改写后是这样的:
async function getOrderDetail(userId) {
try {
const user = await loadUser(userId);
const orders = await loadOrders(user.id);
const detail = await loadOrderDetail(orders[0].id);
console.log(detail);
} catch (err) {
console.log(' 出错了:', err);
}
}
是不是像写同步代码一样?没有.then()的链条后,逻辑变得一目了然。出错了就用 try/catch,跟同步代码处理错误的方式一样。
这玩意儿一出来,我立马就在项目里用了。同事看了都说:“这代码也太好懂了吧!”
四、总结:异步编程的进化,就是为了少掉头发
从回调函数的 “金字塔”,到 Promise 的链式调用,再到 async/await 的 “同步写法”,JavaScript 异步编程的每一步进化,都在解决一个核心问题:让代码更易写、易读、易维护。
现在开发,我基本都是 async/await 为主,偶尔用 Promise.all () 处理并行任务。回调函数?除非维护老项目,否则碰都不想碰。
记住:没有最好的方式,只有最合适的场景。但了解这一路的进化,能让你在写代码时更有底气。
希望这篇文章能帮到你,有问题评论区见~
评论