文章目录

JavaScript 异步编程:回调函数与 Promise

发布于 2026-04-10 03:22:20 · 浏览 7 次 · 评论 0 条

JavaScript 异步编程:回调函数与 Promise

JavaScript 是一门单线程语言,这意味着它同一时间只能做一件事。如果在执行耗时操作(如网络请求、文件读取)时阻塞了主线程,整个页面就会像“死机”一样无法响应。为了解决这个问题,我们需要掌握异步编程的两个核心概念:回调函数与 Promise。


理解回调函数

回调函数是异步编程的基石。简单来说,它就是把一个函数作为参数传给另一个函数,等到事情做完了,再回过头来调用这个函数。

打开你的代码编辑器,输入以下代码来模拟一个耗时操作。

function getData(callback) {
    console.log('1. 开始获取数据...');
    // 模拟 1 秒的耗时操作
    setTimeout(() => {
        const data = '这是用户数据';
        console.log('2. 数据获取完成');
        // 调用回调函数,将数据传回去
        callback(data);
    }, 1000);
}

console.log('3. 代码继续向下执行...');

// 调用函数并传入回调
getData(function(result) {
    console.log('4. 回调函数执行:', result);
});

运行上述代码,你会看到控制台输出的顺序是:1 -> 3 -> 2 -> 4。这说明主线程并没有等待 setTimeout 结束,而是继续往下执行了。


回调地狱的问题

当需要连续执行多个异步操作时,比如“先验证用户 -> 再获取订单 -> 再获取详情”,回调函数必须层层嵌套。代码会变成一个不断向右延伸的三角形,这就是俗称的“回调地狱”。

阅读下面的代码,感受一下这种写法带来的视觉压迫感和维护难度。

function step1(callback) {
    setTimeout(() => callback('结果 A'), 500);
}

function step2(input, callback) {
    setTimeout(() => callback(input + ' -> 结果 B'), 500);
}

function step3(input, callback) {
    setTimeout(() => callback(input + ' -> 结果 C'), 500);
}

// 层层嵌套
step1(function(res1) {
    console.log(res1);
    step2(res1, function(res2) {
        console.log(res2);
        step3(res2, function(res3) {
            console.log(res3);
            // 想要在这里处理错误非常麻烦
        });
    });
});

为了解决这个问题,ES6 引入了 Promise


使用 Promise 重构

Promise 是一个代表了异步操作最终完成或失败的对象。它有三种状态:

  • Pending(进行中)
  • Fulfilled(已成功)
  • Rejected(已失败)

状态一旦改变,就不会再变。我们可以通过 .then() 方法处理成功结果,通过 .catch() 方法处理错误。

重写上面的代码,使用 Promise 将嵌套结构改为链式调用。

function promiseStep1() {
    return new Promise((resolve) => {
        setTimeout(() => resolve('结果 A'), 500);
    });
}

function promiseStep2(input) {
    return new Promise((resolve) => {
        setTimeout(() => resolve(input + ' -> 结果 B'), 500);
    });
}

function promiseStep3(input) {
    return new Promise((resolve) => {
        setTimeout(() => resolve(input + ' -> 结果 C'), 500);
    });
}

// 链式调用,像流水线一样清晰
promiseStep1()
    .then(res => {
        console.log(res);
        return promiseStep2(res); // 返回新的 Promise
    })
    .then(res => {
        console.log(res);
        return promiseStep3(res);
    })
    .then(res => {
        console.log(res);
    })
    .catch(err => {
        console.error('发生错误:', err);
    });

对比两种写法,Promise 链式调用的逻辑是线性的,从上往下读即可,代码层级更扁平。

为了更直观地理解 Promise 的状态流转,我们可以参考下面的状态机逻辑:

graph LR P["状态: Pending"] -- "resolve" --> F["状态: Fulfilled"] P -- "reject" --> R["状态: Rejected"] F -- ".then" --> FN["执行回调: onFulfilled"] R -- ".catch" --> RN["执行回调: onRejected"]

实战对比:错误处理

在回调函数中,通常约定第一个参数传递错误对象(Error-first callback),但开发者很容易忘记处理错误。而在 Promise 中,任何一步发生的错误都会自动向后传递,直到被 .catch() 捕获。

创建一个包含错误的场景,观察 Promise 的容错能力。

  1. 定义一个会报错的函数 simulateError
  2. 编写一个正常函数 simulateSuccess
  3. 组合调用它们。
function simulateError() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('出错了:网络连接失败');
        }, 300);
    });
}

function simulateSuccess(data) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('处理成功: ' + data);
        }, 300);
    });
}

// 即使第二步报错,我们也可以统一在最后处理
simulateError()
    .then(res => {
        // 这一步不会执行,因为上一步报错了
        return simulateSuccess(res);
    })
    .then(res => {
        console.log(res);
    })
    .catch(err => {
        // 错误穿透到这里被捕获
        console.error('捕获到异常:', err);
    });

运行代码,你会发现控制台直接输出了捕获到的错误信息。这就是 Promise 强大的“错误冒泡”机制。


核心差异总结

为了方便记忆,我们将回调函数与 Promise 的核心区别整理如下。

特性 回调函数 Promise
代码结构 容易形成“回调地狱”,嵌套深 链式调用,结构扁平,易读
错误处理 需要在每层手动判断并传递错误 具备错误冒泡机制,统一捕获
执行控制 依赖外部函数调用,控制权分散 通过 resolve/reject 掌握状态控制
可组合性 难以组合,容易混乱 支持Promise.all/race,易于组合

进阶技巧:Promise.all

当需要同时发起多个独立的异步请求,并且必须等它们全部完成后才进行下一步时,使用 Promise.all

执行以下代码,体验并行请求的高效。

const task1 = new Promise(resolve => setTimeout(() => resolve('任务 1 完成'), 1000));
const task2 = new Promise(resolve => setTimeout(() => resolve('任务 2 完成'), 2000));
const task3 = new Promise(resolve => setTimeout(() => resolve('任务 3 完成'), 1500));

// 并行执行,总耗时取决于最慢的任务(2000ms)
Promise.all([task1, task2, task3])
    .then(results => {
        console.log('所有任务结束:', results);
        // 输出: ['任务 1 完成', '任务 2 完成', '任务 3 完成']
    });

如果使用回调函数实现相同的功能,你需要手动计算计数器,代码量会翻倍且容易出错。Promise.all 让并行操作变得极其简单。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文