Promise

Promise 全方位深度讲解

Promise 是 JavaScript 异步编程的核心机制。下面我会从概念、用法、API、原理实现、手写源码、应用场景、常见坑七方面,给你讲透。


一、概念与产生背景

JavaScript 是单线程的,处理异步任务(如网络请求、定时器)的传统方式是回调函数。但嵌套回调会导致"回调地狱":

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

Promise 就是这个问题的解决方案。它代表一个将来会完成(或失败)的异步操作的结果。它是一个有状态的对象,可链式调用,并统一错误处理。


二、核心概念:状态与不可变性

一个 Promise 实例有三种状态:

状态 含义 可转变?
pending (待定) 初始状态,既不是成功也不是失败 ---
fulfilled (已兑现) 操作成功完成 不可逆
rejected (已拒绝) 操作失败 不可逆

状态流转:pending → fulfilled(调用 resolve

      pending → rejected(调用 reject

一旦状态改变就凝固,不能再变。

javascript 复制代码
const p = new Promise((resolve, reject) => {
  resolve('成功');
  reject('失败');   // 无效,状态已是 fulfilled
  resolve('二次');  // 同样无效
});
p.then(console.log); // 只会输出 "成功"

三、构造函数与参数

javascript 复制代码
new Promise(executor)
  • executor 是带有 resolvereject 两个参数的函数。
  • executor 立即同步执行,Promise 构造函数内部会执行它。
  • resolve(value):将 Promise 状态改为 fulfilled,value 作为结果。
  • reject(reason):将 Promise 状态改为 rejected,reason 作为原因。
  • 如果 executor 执行过程抛出异常,Promise 会自动 reject 这个错误。
javascript 复制代码
const p = new Promise((resolve, reject) => {
  console.log('1: executor 立即执行');
  setTimeout(() => {
    resolve('done');
  }, 1000);
});
console.log('2: 主线程继续');
// 输出顺序:1 -> 2 -> (1秒后) .then 回调

四、实例方法详解

1. Promise.prototype.then(onFulfilled, onRejected)

  • 返回一个新的 Promise,实现链式调用。
  • onFulfilled:成功后调用的函数,参数是决议值。
  • onRejected:失败后调用的函数,参数是拒绝原因。
  • 如果 onFulfilled/onRejected 返回一个值 x,则 then 返回的新 Promise 会 resolve(x)
  • 如果返回一个 Promise,则会等待它 settle,其状态和值将传递给下一个 then。
  • 如果抛出异常,新 Promise 变为 rejected。
javascript 复制代码
Promise.resolve(1)
  .then(val => val * 2)        // 返回 2,下一个 then 获得值 2
  .then(val => Promise.resolve(val + 3)) // 返回 Promise,会展开
  .then(console.log);          // 输出 5

2. Promise.prototype.catch(onRejected)

等价于 .then(null, onRejected)。专门捕获前面链中抛出的错误或 reject 的原因。

javascript 复制代码
Promise.reject('出错了')
  .catch(err => {
    console.error(err);
    return '已处理';
  })
  .then(console.log); // 输出 "已处理"

3. Promise.prototype.finally(onFinally)

  • 不论 Promise 是 fulfilled 还是 rejected,最终都会执行 onFinally
  • 回调不接收任何参数。
  • 返回一个新的 Promise,其状态和值继承自原 Promise ,除非 onFinally 中抛出异常或返回一个 rejected 的 Promise。
javascript 复制代码
fetch(url)
  .then(res => res.json())
  .catch(err => console.error(err))
  .finally(() => {
    console.log('请求结束(无论成功或失败)');
  });

注意:finally 的回调返回非 Promise 值不会影响后面的值传递。


五、静态方法详细

1. Promise.resolve(value)

  • 如果 value 是一个 Promise,直接返回它。
  • 如果 value 是 thenable 对象(有 then 方法),会尝试展开(可能异步)。
  • 其它情况,返回一个 fulfilled 状态的 Promise。
javascript 复制代码
Promise.resolve(42).then(console.log); // 42

const thenable = {
  then(resolve) { resolve('thenable'); }
};
Promise.resolve(thenable).then(console.log); // 'thenable'

2. Promise.reject(reason)

返回一个 rejected 状态的 Promise,原因就是 reason(不会展开 thenable)。

javascript 复制代码
Promise.reject(new Error('失败')).catch(err => console.error(err.message));

3. Promise.all(iterable)

  • 接收一个可迭代对象(通常是数组),里面是一组 Promise。
  • 所有 Promise 都 fulfilled,返回的 Promise 才会 fulfilled,结果是一个按原顺序排列的结果数组。
  • 只要有一个 Promise 被 rejected,立即整体 rejected,原因就是那个失败的原因(快速失败)。
javascript 复制代码
const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => setTimeout(resolve, 100, 2));
const p3 = Promise.reject('error');

Promise.all([p1, p2]).then(console.log); // [1, 2] (100ms 后)
Promise.all([p1, p2, p3]).catch(console.log); // 输出 'error',不会等 p2

4. Promise.allSettled(iterable)

  • 所有 Promise 都敲定(无论 fulfilled 还是 rejected)后才返回。
  • 结果是一个对象数组,每项都有 statusvalue/reason
javascript 复制代码
Promise.allSettled([p1, p2, p3]).then(results => {
  // [
  //   { status: 'fulfilled', value: 1 },
  //   { status: 'fulfilled', value: 2 },
  //   { status: 'rejected', reason: 'error' }
  // ]
});

应用场景:当你需要并发多个任务,哪怕某些失败也要获取其他结果时(如批量请求数据后整合展示)。

5. Promise.race(iterable)

  • 返回的 Promise 会跟随第一个敲定的 Promise 的状态和值(无论成功或失败)。
  • 竞赛:多个 Promise 抢跑,最先到的决定结果。
javascript 复制代码
const fast = new Promise(resolve => setTimeout(resolve, 50, '快'));
const slow = new Promise(resolve => setTimeout(resolve, 100, '慢'));
Promise.race([fast, slow]).then(console.log); // '快',50ms

常用于设置超时:

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

6. Promise.any(iterable)

  • all 相反,只要有一个 fulfilled,结果就是那个值;如果全部 rejected,返回的 Promise 会 rejected,并带有一个 AggregateError
  • 类似于竞赛,但寻找第一个成功的。
javascript 复制代码
Promise.any([Promise.reject('err1'), Promise.resolve('成功')])
  .then(console.log); // '成功'

Promise.any([Promise.reject('a'), Promise.reject('b')])
  .catch(e => {
    console.log(e.errors); // ['a', 'b']
  });

六、实现原理与手写Promise

要真正理解 Promise,需要自己实现一个简易且符合 Promise/A+ 规范的版本。

核心原理:

  • Promise 是一个状态机(pending、fulfilled、rejected)。
  • then 方法会注册回调,如果状态还是 pending,就存入队列;如果已经是终态,异步执行(微任务)回调。
  • then 返回一个新的 Promise,供链式调用。这个新 Promise 的状态取决于回调函数的返回值。
  • resolve 过程如果接收到 Promise 或 thenable,需要递归展开。
  • 所有的回调都需要异步调用 (通常用 queueMicrotasksetTimeout 模拟微任务)。

简易实现:

javascript 复制代码
class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;   // 成功值
    this.reason = undefined;  // 失败原因
    this.onFulfilledCallbacks = []; // 成功回调队列
    this.onRejectedCallbacks = [];  // 失败回调队列

    const resolve = (value) => {
      if (this.state !== 'pending') return;
      // 如果 resolve 的是一个 Promise,需要递归展开
      if (value instanceof MyPromise) {
        return value.then(resolve, reject);
      }
      this.state = 'fulfilled';
      this.value = value;
      this.onFulfilledCallbacks.forEach(fn => fn());
    };

    const reject = (reason) => {
      if (this.state !== 'pending') return;
      this.state = 'rejected';
      this.reason = reason;
      this.onRejectedCallbacks.forEach(fn => fn());
    };

    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  then(onFulfilled, onRejected) {
    // 值穿透:如果没传 onFulfilled,默认把值往后传
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // 如果没传 onRejected,默认抛出错误,让后面的 catch 捕获
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

    const promise2 = new MyPromise((resolve, reject) => {
      // 统一处理回调,根据返回值 x 决定 promise2 的状态
      const fulfillMicrotask = () => {
        // 用微任务保证异步
        queueMicrotask(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      };

      const rejectMicrotask = () => {
        queueMicrotask(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      };

      if (this.state === 'fulfilled') {
        fulfillMicrotask();
      } else if (this.state === 'rejected') {
        rejectMicrotask();
      } else { // pending 状态,存入队列
        this.onFulfilledCallbacks.push(fulfillMicrotask);
        this.onRejectedCallbacks.push(rejectMicrotask);
      }
    });

    return promise2;
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  finally(onFinally) {
    return this.then(
      value => MyPromise.resolve(onFinally()).then(() => value),
      reason => MyPromise.resolve(onFinally()).then(() => { throw reason; })
    );
  }

  static resolve(value) {
    if (value instanceof MyPromise) return value;
    return new MyPromise(resolve => resolve(value));
  }

  static reject(reason) {
    return new MyPromise((_, reject) => reject(reason));
  }

  // ... 其他静态方法 all/race 等类似,篇幅所限不全部展开
}

// 处理 then 返回值和 promise2 的关系(Promise 解决过程)
function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected'));
  }
  let called = false; // 防止多次调用
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      const then = x.then;
      if (typeof then === 'function') {
        then.call(x,
          y => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          r => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
}

要点解读:

  • 立即执行 executor,提供 resolve/reject
  • 状态管理:一旦改变,不能再变。
  • 异步回调 :所有 then 的回调都用 queueMicrotask 延迟,确保在解决(resolve/reject)后仍异步。
  • 链式与返回值then 返回新的 Promise,依据回调返回值 x 决定新 Promise 状态。resolvePromise 函数实现完整的 Promise 解决过程,包括对 thenable 的递归展开和循环引用检查。
  • 值穿透then().then().then(v => console.log(v)) 能正常工作,因为默认函数传值/抛错。

七、Promise 在事件循环中的位置

Promise 的 .then/catch/finally 回调被放入微任务队列(microtask queue)。微任务在每轮宏任务执行完后、下一个宏任务之前清空。

javascript 复制代码
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// 输出顺序:1 4 3 2

理解这点对避免异步顺序的坑至关重要。


八、应用场景

  1. 网络请求:fetch/Axios 都基于 Promise。
  2. 顺序串行执行 :链式 .thenasync/await
  3. 并发控制Promise.all 处理多个并行任务。
  4. 竞速与超时Promise.race 实现请求超时放弃。
  5. 错误重试:封装 retry 函数,失败后递归返回 Promise。
  6. 缓存与共享 :将异步结果保存,多次 .then 获取同一个状态。

九、常见坑与避坑指南

1. 忽略错误导致 Promise 静默失败

javascript 复制代码
// 没有 catch,这个 rejected 将成为未处理的 promise rejection
Promise.reject('error');   // 在 Node 中可能终止进程

解决 :链条末尾加 .catch(),或使用全局 unhandledrejection 监听。

2. then 中忘记 return

javascript 复制代码
Promise.resolve(1)
  .then(v => { v + 1; })  // 没有 return,返回 undefined
  .then(console.log);     // undefined

3. Promise.all 快速失败导致未完成的请求可能仍运行

Promise.all 一旦 reject,其他请求不会被取消(因为 Promise 无法取消)。若有取消需求,需使用 AbortController(fetch)或外部状态控制。

4. then 中传入非函数参数会被忽略

then('foo') 相当于 then(null),不能处理值,需传入函数。

5. finally 的返回值"陷阱"

javascript 复制代码
Promise.resolve(10)
  .finally(() => 20) // 这里返回的 20 被忽略
  .then(console.log); // 10

finally 回调的返回值(非 rejected Promise)不会改变链中的值。

6. 循环中用 async/await 可能失去并发

javascript 复制代码
[1,2,3].forEach(async (id) => {
  await fetchData(id); // 并发执行,但 forEach 不会等它们完成
});

改用 for...of 结合 await 实现顺序,或用 Promise.all(items.map(async ...)) 实现并发等待。

7. Promise.resolve 传递 thenable 可能造成意外的异步

javascript 复制代码
const thenable = {
  then(resolve) { console.log('side effect'); resolve(1); }
};
Promise.resolve(thenable); // 立即输出 'side effect',并异步 resolve

这是特性,但要留意。

8. 滥用 new Promise 包裹已有 Promise

javascript 复制代码
// 反模式
function fetchData() {
  return new Promise((resolve, reject) => {
    fetch(url).then(resolve).catch(reject); // 多余,直接 return fetch(url) 即可
  });
}

避免不必要的包装,直接返回或链式处理。


十、总结

  • Promise 解决了回调地狱,提供链式与统一错误处理。
  • 三种状态,一旦敲定,终身不变。
  • then/catch/finally 构建链式流程,静态方法处理集合。
  • 所有 then 回调都在微任务中执行。
  • 手写 Promise 时要关注状态、异步、链式、thenable 展开和循环引用。

现在,你可以自信地使用和讲解 Promise 了。如果需要,我还可以展开 async/await 与 Promise 的关系和底层实现。

相关推荐
怕浪猫1 小时前
Electron 开发实战(十一):自动更新机制|服务架构、公私网更新、版本回滚全解
前端·javascript·electron
AI视觉网奇1 小时前
three-bvh-csg glb分割
开发语言·前端·javascript
很楠爱上1 小时前
TypeScript 核心知识精要
javascript·ubuntu·typescript
zhangfeng11331 小时前
workbuddy ,node.js 每次会在 项目目录上安装 node_modules,能不能一次安装多次使用,为什么 npm 不把包装在全局
前端·npm·node.js
一次旅行1 小时前
CopilotKit实战:用生成式UI打造智能Agent前端
前端·人工智能·ui
禅思院1 小时前
大列表性能优化 · 工程实战·四
开发语言·前端·性能优化·前端框架·php·异步加载
rising start1 小时前
五、Vue3 ref 用法 + Props 完整指南
前端·javascript·vue.js
web打印社区1 小时前
前端html转换pdf并静默打印pdf最佳实现路径
前端·javascript·vue.js·electron·html
Curvatureflight2 小时前
浏览器音频采集实践:麦克风权限、降噪、回声消除与 PCM 转换
前端·javascript·音视频·信息与通信·web·pcm