JavaScript 异步编程深入浅出:从回调地狱到 async/await 的进化之路 | 10 年开发经验解析

大家好,我是第八哥,专注前端开发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 () 处理并行任务。回调函数?除非维护老项目,否则碰都不想碰。

记住:没有最好的方式,只有最合适的场景。但了解这一路的进化,能让你在写代码时更有底气。

希望这篇文章能帮到你,有问题评论区见~

上一篇 JavaScript开发必看:10个常见陷阱及避坑指南 | 附完整示例代码解析 下一篇 JavaScript 错误处理艺术:从 try-catch 到全局监控的实战指南与异常捕获技巧

评论

暂不支持评论