今天我们来聊聊 JavaScript 异步编程的演进。如果你写过嵌套多层的回调函数,一定懂那种 “回调地狱” 的痛苦。从 ES6 开始,Promise 和后续的 async/await 彻底改变了我们处理异步代码的方式,让异步逻辑变得清晰、易读。

一、同步和异步

在聊 Promise 之前,我们得先分清同步异步的区别,这是理解一切的基础。

同步:代码从上到下排队执行,前一个任务没完成,后面的任务就只能等着。

异步:耗时的任务会在后台执行,主线程不用干等着,可以继续往下走。等任务完成后,再通过回调通知主线程。

在 JavaScript 中,我们经常会遇到异步场景,比如:定时器 setTimeout / setInterval、网络请求(fetch、Axios)、文件读取 / 写入、DOM 事件监听(clickload

二、回调地狱

在 Promise 出现之前,JavaScript 处理异步只能用回调函数。当多个异步操作需要按顺序执行时,代码就会像 “套娃” 一样嵌套下去,这就是回调地狱

// 经典的回调地狱
getUser('123', (user) => {
  getOrder(user.id, (order) => {
    getLogistics(order.id, (logistics) => {
      getGoods(logistics.goodsId, (goods) => {
        console.log(goods);
      }, (err) => { console.log('物流失败', err); });
    }, (err) => { console.log('订单失败', err); });
  }, (err) => { console.log('用户失败', err); });
}, (err) => { console.log('请求失败', err); });

这种代码不仅嵌套深、可读性差,而且错误处理分散,每一层都要单独写错误回调,非常容易遗漏。

三、promise

1.promise是什么

Promise 是 ES6 新增的JS 内置对象,本质是一个「异步操作的状态容器 + 结果管理器」。它的核心使命是:封装异步操作,管理异步操作的状态,统一异步结果的处理方式

2、核心原理

Promise 解决回调地狱的核心是「状态不可逆 + 回调延迟绑定 + 链式调用」

(1)状态不可逆机制

Promise 有且仅有 3 种状态 :pending(等待中)、fulfilled(已成功)、rejected(已失败)且只能从 pending 转为 fulfilled/rejected,状态固化后结果永久保存。

const p = new Promise((resolve, reject) => { 
  resolve('成功结果'); // 状态从pending→fulfilled并固化 
  reject('无效失败');  // 状态已固化,此调用完全无效  
  // 关键:后续无论调用多少次then/catch,都能拿到"成功结果"
});
(2)回调函数延迟绑定

Promise 不会像传统回调那样「立即执行回调」,而是把回调函数(then/catch 里的逻辑)延迟到异步操作状态确定后再执行:创建 Promise 时,异步操作进入后台执行,调用 then/catch 时,回调函数会被存入 Promise 的「回调队列」。当异步操作完成(状态改变),Promise 会遍历回调队列,按顺序执行回调,并把结果传入

const p = new Promise(resolve => setTimeout(() => resolve('结果'), 1000));
p.then(res => console.log(res)); // 回调先入队列,等状态确定后执行
// 核心:异步未完成时,then/catch回调不会立即执行
(3)链式调用的底层逻辑

then/catch 每次执行后,都会返回一个全新的 Promise 实例(而非原实例),这是链式调用的核心。╰(*°▽°*)╯

1、then 里返回普通值(数字、对象等),新 Promise 状态为 fulfilled,值为这个普通值;

2、then 里返回另一个 Promise,新 Promise 会继承这个 Promise 的状态和结果;

3、 then 里抛出异常,新 Promise 状态为 rejected,值为异常信息。

p.then(res => {
  return '新值'; // 返回普通值→新Promise状态为fulfilled
  // return Promise.resolve('新Promise'); // 继承新Promise状态
  // throw new Error('异常'); // 抛出异常→新Promise状态为rejected
}).then(newRes => console.log(newRes)); // 链式调用依赖返回新Promise

3、核心语法

1. 创建 Promise 实例
// 标准写法:executor 函数接收 resolve/reject 两个参数
const p = new Promise((resolve, reject) => {
  // 异步操作:比如网络请求、文件读取
  const request = new XMLHttpRequest();
  request.open('GET', 'https://api.example.com/user');
  request.onload = () => {
    if (request.status === 200) {
      // 成功:调用 resolve,传入响应数据
      resolve(JSON.parse(request.responseText));
    } else {
      // 失败:调用 reject,传入错误信息
      reject(new Error(`请求失败:${request.status}`));
    }
  };
  request.onerror = () => {
    reject(new Error('网络错误'));
  };
  request.send();
});
2. 实例方法

(1)then ():核心处理方法

const p = new Promise(resolve => resolve('成功数据'));
p.then((data) => {
  console.log('成功:', data); // 输出:成功: 成功数据
  return '新数据'; // 返回普通值,自动包装成成功Promise
}).then((newData) => {
  console.log('上一个then的返回值:', newData); // 输出:新数据
});

为什么不推荐用 then 的第二个参数处理失败?因为它无法捕获「第一个参数(成功回调)」内部抛出的异常,而 catch 可以捕获整个链式调用中所有的异常。

(2)catch ():统一错误处理

const p = new Promise((resolve, reject) => reject('失败原因'));
p.catch((err) => {
  console.log('失败:', err); // 输出:失败: 失败原因
});

// 重要:catch 还能捕获 then 里的错误
p.then(() => {
  throw new Error('then里的错误');
}).catch((err) => {
  console.log('捕获到错误:', err); // 输出:捕获到错误: Error: then里的错误
});

(3)finally():无论成功 / 失败都执行(收尾操作)

const p = new Promise(resolve => resolve('成功'));
p.then(data => console.log(data))
  .catch(err => console.log(err))
  .finally(() => {
    console.log('异步操作结束,比如关闭加载动画'); // 必执行
  });

四、async 和 await

async/await 是 ES7 新增的语法,基于 Promise 封装,是「异步代码同步化」的终极方案 —— 让异步代码看起来和同步代码一样,彻底告别链式调用。async:修饰函数,让函数返回一个 Promise 实例;await:只能在 async 函数里使用,等待 Promise 完成(成功 / 失败),并直接拿到 Promise 的结果(不用写 then)。

1、基本用法

// 先定义返回 Promise 的异步函数
function getUser() {
  return new Promise(resolve => resolve({ id: '123', name: '张三' }));
}

// 用 async/await 重构
async function getInfo() {
  // 核心:await 直接拿到 Promise 的成功结果,代码像同步一样
  const user = await getUser(); 
  console.log('用户信息:', user); // 输出:用户信息: { id: '123', name: '张三' }
  return user; // async函数返回的普通值,会自动包装成 Promise
}

// 调用 async 函数
getInfo().then(user => console.log('最终结果:', user));

2、错误处理(try/catch)

async function getInfo() {
  try {
    // 把可能出错的 await 代码放在 try 里
    const user = await new Promise((resolve, reject) => reject('请求失败'));
    console.log(user);
  } catch (err) {
    // 用 catch 捕获错误(替代 Promise 的 catch)
    console.log('捕获到错误:', err); // 输出:捕获到错误: 请求失败
  }
}
getInfo();

3、对比 Promise 链式调用

async/await 重构之前的多层异步代码,更简洁:

async function getGoodsInfo() {
  try {
    const user = await getUser('123');
    const order = await getOrder(user.id);
    const logistics = await getLogistics(order.id);
    const goods = await getGoods(logistics.goodsId);
    console.log('最终商品信息:', goods);
  } catch (err) {
    console.log('任意一步出错:', err);
  }
}
getGoodsInfo();

五、总结

JS 异步编程从 “回调地狱” 的嵌套噩梦,到 ES6 Promise 用「状态管理 + 链式调用」实现异步逻辑扁平化,再到 ES7 async/await 基于 Promise 打造 “异步代码同步写法”,核心演进目标始终是让异步代码更易读、易维护。Promise 靠状态不可逆、回调延迟绑定解决回调嵌套问题,而 async/await 则进一步简化语法,通过 try/catch 统一错误处理,成为当前前端异步编程的主流方案。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐