深入理解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。
相关推荐
To_OC7 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic8 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy8 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志8 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble9 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长10 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯10 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜11 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司11 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
江华森12 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http