深入理解ES6 Promise

一、为什么需要 Promise?

在 JavaScript 中,异步操作(如网络请求、文件读取、定时器)通常通过回调函数处理。但多个异步操作依赖或串行时,会出现回调地狱

javascript 复制代码
// 回调地狱示例
setTimeout(() => {
  console.log('第一步');
  setTimeout(() => {
    console.log('第二步');
    setTimeout(() => {
      console.log('第三步');
    }, 1000);
  }, 1000);
}, 1000);

回调地狱的缺点:

  • 代码横向增长,可读性差
  • 难以捕获错误
  • 难以并行或竞速处理多个异步任务

Promise 正是为了解决这些问题而诞生的,它让异步代码更接近同步写法的风格,且提供了统一的错误处理机制。


二、Promise 是什么?

Promise 是 ES6 新增的一个内置构造函数,代表一个异步操作的最终完成(或失败)及其结果值

字面理解:Promise 是一个"承诺",承诺在未来某个时刻会给你一个结果(成功或失败)。

一个 Promise 对象处于以下三种状态之一:

状态 含义 特点
pending 初始状态,未完成也未拒绝 可以转变为 fulfilledrejected
fulfilled 操作成功完成 有一个 value(结果值)
rejected 操作失败 有一个 reason(失败原因)

状态一旦改变,就永久固定,不会再变。


三、Promise 的基本用法

1. 创建一个 Promise

使用 new Promise(executor),其中 executor 是一个立即执行的函数,它接收两个参数:resolvereject

javascript 复制代码
const promise = new Promise((resolve, reject) => {
  // 异步操作
  const success = true;
  setTimeout(() => {
    if (success) {
      resolve('成功的数据'); // 将状态改为 fulfilled
    } else {
      reject('失败的原因');  // 将状态改为 rejected
    }
  }, 1000);
});

2. 使用 .then().catch() 消费 Promise

javascript 复制代码
promise
  .then((value) => {
    console.log('成功:', value);
  })
  .catch((error) => {
    console.error('失败:', error);
  });
  • .then() 接收两个可选参数:onFulfilledonRejected。通常只传第一个,错误交给 .catch()
  • .catch().then(null, onRejected) 的语法糖。

3. .finally() ------ 无论成功失败都会执行

javascript 复制代码
promise
  .then(value => console.log(value))
  .catch(error => console.error(error))
  .finally(() => console.log('操作结束,清理资源'));

四、Promise 的链式调用(核心特性)

.then().catch() 返回一个新的 Promise,因此可以串联多个异步操作。

javascript 复制代码
new Promise((resolve) => {
  resolve(1);
})
  .then((value) => {
    console.log(value); // 1
    return value + 1;
  })
  .then((value) => {
    console.log(value); // 2
    return value + 1;
  })
  .then((value) => {
    console.log(value); // 3
  });

链式调用中的返回值处理规则:

  • 如果回调函数返回一个普通值 (包括 undefined),它会自动被包装成一个 fulfilled 的 Promise。
  • 如果返回一个 Promise ,那么下一个 .then() 会等待这个 Promise 的结果。
  • 如果抛出异常throw new Error()),会进入下一个 .catch()

示例:模拟异步任务顺序执行

javascript 复制代码
function asyncTask(name, duration) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(`${name} 完成`);
      resolve(name);
    }, duration);
  });
}

asyncTask('任务1', 1000)
  .then(() => asyncTask('任务2', 500))
  .then(() => asyncTask('任务3', 800))
  .then(() => console.log('所有任务完成'));

五、Promise 的静态方法

1. Promise.resolve(value)

快速返回一个状态为 fulfilled 的 Promise。

javascript 复制代码
Promise.resolve('直接成功').then(console.log); // 直接成功

如果 value 本身是一个 Promise,则直接返回该 Promise。

2. Promise.reject(reason)

快速返回一个状态为 rejected 的 Promise。

javascript 复制代码
Promise.reject('出错了').catch(console.error); // 出错了

3. Promise.all(iterable)

并行执行多个 Promise,所有都成功 才成功,结果按顺序组成数组;任何一个失败则立即失败,并返回第一个失败的原因。

javascript 复制代码
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = new Promise((resolve) => setTimeout(resolve, 1000, 3));

Promise.all([p1, p2, p3]).then((results) => {
  console.log(results); // [1, 2, 3]  (等待 1 秒后输出)
});

失败示例:

javascript 复制代码
const p1 = Promise.resolve(1);
const p2 = Promise.reject('错误');

Promise.all([p1, p2]).catch((err) => {
  console.log(err); // '错误'
});

4. Promise.race(iterable)

竞速:返回最先 settled(成功或失败)的那个 Promise 的结果

javascript 复制代码
const p1 = new Promise((resolve) => setTimeout(resolve, 500, '慢'));
const p2 = new Promise((resolve) => setTimeout(resolve, 100, '快'));

Promise.race([p1, p2]).then(console.log); // '快'

常用于设置超时:

javascript 复制代码
function fetchWithTimeout(url, timeout = 5000) {
  const fetchPromise = fetch(url);
  const timeoutPromise = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('请求超时')), timeout)
  );
  return Promise.race([fetchPromise, timeoutPromise]);
}

5. Promise.allSettled(iterable) (ES2020)

等待所有 Promise 都 settle(成功或失败),返回每个对象的状态和结果(或原因)。永远不会进入 .catch()

javascript 复制代码
const promises = [
  Promise.resolve('成功'),
  Promise.reject('失败'),
  Promise.resolve('另一个成功'),
];

Promise.allSettled(promises).then((results) => {
  console.log(results);
  /*
  [
    { status: 'fulfilled', value: '成功' },
    { status: 'rejected', reason: '失败' },
    { status: 'fulfilled', value: '另一个成功' }
  ]
  */
});

6. Promise.any(iterable) (ES2021)

只要有一个 Promise 成功就返回那个成功的结果;如果所有都失败 ,则返回一个 AggregateError(聚合错误)。

javascript 复制代码
const p1 = Promise.reject('错误1');
const p2 = Promise.reject('错误2');
const p3 = Promise.resolve('成功');

Promise.any([p1, p2, p3]).then(console.log); // '成功'

全部失败的情况:

javascript 复制代码
Promise.any([Promise.reject('a'), Promise.reject('b')])
  .catch((err) => {
    console.log(err instanceof AggregateError); // true
    console.log(err.errors); // ['a', 'b']
  });

六、错误处理的最佳实践

  1. 总是添加 .catch() 或者在每个 .then() 的第二个参数中处理错误,否则错误会被静默吞没(虽然浏览器会报 unhandled rejection,但不推荐依赖于此)。

  2. 在链式调用的末尾统一捕获错误

javascript 复制代码
doSomething()
  .then(doSomethingElse)
  .then(doThirdThing)
  .catch(handleError); // 捕获前面任意一步的错误
  1. 不要在 .then() 中抛出普通字符串 ,而应该抛出 Error 对象,以便获得堆栈信息。

  2. async/await 与 try-catch 是 Promise 的语法糖,但底层依然是 Promise,理解 Promise 是掌握 async/await 的基础。


七、Promise 与回调地狱对比

回调地狱

javascript 复制代码
getUser((user) => {
  getPosts(user.id, (posts) => {
    getComments(posts[0].id, (comments) => {
      console.log(comments);
    });
  });
});

Promise 版本(假设每个函数都返回 Promise):

javascript 复制代码
getUser()
  .then(user => getPosts(user.id))
  .then(posts => getComments(posts[0].id))
  .then(comments => console.log(comments))
  .catch(error => console.error(error));

扁平化,更易读。


八、进阶示例:串行与并行控制

1. 串行执行一组异步任务(逐个进行)

javascript 复制代码
const tasks = [task1, task2, task3]; // 每个 task 返回 Promise
tasks.reduce((prevPromise, task) => {
  return prevPromise.then(() => task());
}, Promise.resolve());

2. 限制并发数量(例如同时最多 3 个请求)

javascript 复制代码
async function limitConcurrent(tasks, limit) {
  const results = [];
  const executing = [];
  for (const task of tasks) {
    const p = task().then(r => results.push(r));
    executing.push(p);
    if (executing.length >= limit) {
      await Promise.race(executing);
      // 清除已完成的任务
      executing.splice(executing.findIndex(e => e === p), 1);
    }
  }
  await Promise.all(executing);
  return results;
}

九、Promise 的局限性

问题 说明
不可取消 一旦创建 Promise,无法从外部取消它的执行(某些库提供 cancelable promise,但非标准)。
无法获取中间状态 无法同步获取"进度",只能拿到最终结果(不过可以自己实现进度通知)。
错误可能被吞没 若没有 .catch() 且未被 async/await 捕获,错误将静默消失(现代浏览器会打印 unhandledrejection 警告)。
新手容易误解 例如忘记 return 导致链断裂,或在 .then() 中创建新的 Promise 但未返回。

十、总结

  • Promise 是异步编程的基石,它将回调函数的嵌套转化为链式调用。
  • 核心掌握:状态 (pending/fulfilled/rejected)、链式调用静态方法allraceallSettledany)。
  • 搭配 async/await 使用时,Promise 依然在幕后工作,理解 Promise 有助于写出更可靠的异步代码。
  • 实际开发中,几乎所有现代 JavaScript API(如 fetchfs.promisessetTimeout 包装)都基于 Promise。
相关推荐
吴声子夜歌4 小时前
ES6——Module详解
前端·ecmascript·es6
剪刀石头布啊4 小时前
原生form发起表单干了啥
前端
剪刀石头布啊4 小时前
表单校验场景,如何实现页面滚动到报错位置
前端
gyx_这个杀手不太冷静5 小时前
大人工智能时代下前端界面全新开发模式的思考(二)
前端·架构·ai编程
GreenTea5 小时前
AI Agent 评测的下半场:从方法论到落地实践
前端·人工智能·后端
吴声子夜歌5 小时前
Vue3——Vue实例与数据绑定
前端·javascript·vue.js
我是若尘6 小时前
Harness Engineering:2026 年 AI 编程的核心战场
前端·后端·程序员
weixin199701080166 小时前
《快手商品详情页前端性能优化实战》
前端·性能优化
IT_陈寒7 小时前
折腾一天才明白:Vite的热更新为什么偶尔会罢工
前端·人工智能·后端