深入理解 Promise:原理、实现与事件循环中的角色,看这篇就够了

643 阅读8分钟

一、Promise 的设计

1.1 为什么需要 Promise?

在 JavaScript 中,回调函数(callback)是最常用。然而,随着异步场景复杂度的增加,传统回调方式暴露了如下问题:

  1. 回调地狱:
    嵌套层级太深,导致代码难以维护。
    示例:
const readFile = (fileName) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(`读取文件: ${fileName}`);
      if (fileName) resolve(`${fileName} 内容`);
      else reject('文件不存在');
    }, 1000);
  });
};

// 执行多个任务,每个任务依赖于上一个任务的结果
readFile('file1')
  .then((data1) => {
    console.log('处理:', data1);
    return readFile('file2').then((data2) => {
      console.log('处理:', data2);
      return readFile('file3').then((data3) => {
        console.log('处理:', data3);
      });
    });
  })
  .catch((err) => {
    console.error('出错了:', err);
  });
  1. 错误处理困难:
    每个回调函数需要单独处理错误逻辑,且无法统一捕获。
  2. 缺乏组合能力:
    同时执行多个异步操作,并等待它们的结果,回调难以解决。

为了解决这些问题,Promise 通过一套状态管理机制,将异步操作抽象为对象,提供链式调用、统一错误处理以及任务组合等能力。


1.2 Promise 的三大设计原则

  1. 状态管理:
    Promise 有三种状态:pending(进行中)、fulfilled(已完成)和 rejected(已失败)。状态一旦从 pending 转变为 fulfilledrejected,就不可逆。
  2. 链式调用:
    thencatch 方法返回新的 Promise,支持任务按顺序组织。
  3. 基于事件循环:
    Promise 的回调通过微任务队列执行,保证异步代码更加精确。

二、Promise 与事件循环

2.1 JavaScript 的事件循环

要理解 Promise 的执行顺序,首先要理解 JavaScript 的事件循环机制。事件循环主要分为以下两类任务:

  1. 宏任务(MacroTask):
    包括主线程代码、setTimeoutsetInterval、I/O 操作等。
  2. 微任务(MicroTask):
    包括 Promise.thenqueueMicrotaskMutationObserver

执行顺序:

  • 每次事件循环会从 宏任务队列 中取出一个任务执行,随后执行完所有的 微任务
  • 如果微任务队列清空后,主线程仍然空闲,则继续处理下一个宏任务。

2.2 Promise 在事件循环中的角色

Promise 的 .then.catch 注册的回调函数会被加入 微任务队列,优先级高于宏任务。微任务的调度确保了 Promise 的回调在主线程任务结束后立即执行。

示例 1:Promise 和定时器的执行顺序

console.log('A');

setTimeout(() => {
  console.log('B');
}, 0);

Promise.resolve().then(() => {
  console.log('C');
});

console.log('D');

执行顺序:

A
D
C
B

分析:

  1. console.log('A')console.log('D') 是主线程代码,优先执行。
  2. setTimeout 的回调进入宏任务队列。
  3. Promise.then 的回调进入微任务队列。
  4. 主线程任务完成后,先执行微任务(C),再执行宏任务(B)。

示例 2:嵌套微任务与宏任务

console.log('1');

setTimeout(() => {
  console.log('2');
  Promise.resolve().then(() => {
    console.log('3');
  });
}, 0);

Promise.resolve().then(() => {
  console.log('4');
  setTimeout(() => {
    console.log('5');
  }, 0);
});

console.log('6');

执行顺序:

1
6
4
2
3
5

执行过程分析:

  1. 主线程执行:打印 16
  2. setTimeout 回调进入宏任务队列。
  3. Promise.resolve().then(() => console.log('4')) 回调进入微任务队列。
  4. 主线程执行完毕后,清空微任务队列,执行 4
  5. 微任务执行中创建的宏任务(setTimeoutconsole.log('5'))进入宏任务队列。
  6. 执行第一个宏任务,打印 2,同时产生一个新的微任务(console.log('3'))。
  7. 清空新产生的微任务队列,打印 3
  8. 执行最后一个宏任务,打印 5

三、Promise 的实现原理

想深入理解 Promise 的工作机制,还要从其核心实现。

以下是一个符合 Promise/A+ 规范 的简化实现:

3.1 状态管理与回调存储

Promise 的状态管理机制确保其状态不可逆,且每个状态都维护独立的回调队列。

实现:

class MyPromise {
  constructor(executor) {
    this.state = 'pending'; // 初始状态
    this.value = undefined; // 终值或拒因
    this.onFulfilledCallbacks = []; // 成功回调队列
    this.onRejectedCallbacks = [];  // 失败回调队列

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

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

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

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        try {
          const result = onFulfilled(this.value);
          resolve(result);
        } catch (err) {
          reject(err);
        }
      } else if (this.state === 'rejected') {
        try {
          const result = onRejected(this.value);
          resolve(result);
        } catch (err) {
          reject(err);
        }
      } else {
        this.onFulfilledCallbacks.push(() => {
          try {
            const result = onFulfilled(this.value);
            resolve(result);
          } catch (err) {
            reject(err);
          }
        });
        this.onRejectedCallbacks.push(() => {
          try {
            const result = onRejected(this.value);
            resolve(result);
          } catch (err) {
            reject(err);
          }
        });
      }
    });
  }
}

3.2 链式调用的实现细节

链式调用通过 then 方法返回一个新的 Promise 对象实现。返回的 Promise 的状态由 then 回调的执行结果决定:

  1. 如果返回值是普通值,则包装为 Promise.resolve(value)
  2. 如果返回值是一个 Promise,则等待其状态完成后再执行下一步。
  3. 如果抛出错误,则返回的 Promise 状态为 rejected

四、Promise 的性能优化与实际应用

4.1 并发任务的处理

通过 Promise.all 并行执行多个异步任务,并同时获取结果:

const fetchData = (url) => new Promise((resolve) => setTimeout(() => resolve(url), 1000));

Promise.all([fetchData('/api/user'), fetchData('/api/posts')])
  .then(([user, posts]) => {
    console.log('用户信息:', user);
    console.log('帖子信息:', posts);
  });

4.2 超时控制

为异步任务设置超时时间:

const withTimeout = (promise, timeout) => {
  return Promise.race([
    promise,
    new Promise((_, reject) => setTimeout(() => reject('超时'), timeout)),
  ]);
};

五、Promise 类方法

5.1、Promise.resolve

功能

Promise.resolve 将一个值(或非 Promise)包装成一个 已完成的 Promise
如果传入的值是一个 Promise,则直接返回该 Promise。

语法

Promise.resolve(value);

参数

  • value: 任意值。如果是一个 Promise,则原样返回;如果是普通值,则包装为一个 Fulfilled 状态的 Promise。

使用场景

  1. 快速将一个值转化为 Promise,统一处理异步和同步任务。
  2. 用于测试和调试。

示例

// 传入普通值
Promise.resolve(42).then((value) => console.log(value)); // 输出: 42

// 传入 Promise
const promise = Promise.resolve(Promise.resolve('Hello'));
promise.then((value) => console.log(value)); // 输出: Hello

5.2、Promise.reject

功能

Promise.reject 返回一个 已失败的 Promise,并携带指定的错误原因。

语法

Promise.reject(reason);

参数

  • reason: Promise 被拒绝的原因(通常是一个错误对象)。

使用场景

  1. 手动创建一个 Rejected 状态的 Promise。
  2. 模拟错误场景以进行测试。

示例

Promise.reject('Error!')
  .catch((reason) => console.error(reason)); // 输出: Error!

5.3、Promise.all

功能

Promise.all 接受一个包含多个 Promise 的数组,并返回一个新的 Promise。

  • 成功条件:所有 Promise 都 Fulfilled,返回一个包含每个 Promise 结果的数组。
  • 失败条件:只要有一个 Promise 被 Rejected,则返回该 Promise 的拒因。

语法

Promise.all(iterable);

参数

  • iterable: 一个可以迭代的对象(如数组、Set),每一项为一个 Promise。

使用场景

  1. 并行执行多个异步任务,并等待所有任务完成。
  2. 确保依赖关系中所有任务都成功后再继续后续操作。

示例

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
  .then((results) => console.log(results)) // 输出: [1, 2, 3]
  .catch((err) => console.error(err)); // 只要有一个失败,就进入 catch

注意

如果数组中有非 Promise 值,Promise.all 会将其视为 Fulfilled 状态的 Promise:

Promise.all([1, Promise.resolve(2)]).then(console.log); // 输出: [1, 2]

5.4、Promise.allSettled

功能

Promise.allSettled 返回一个新的 Promise,永远成功

  • 它会等待所有的 Promise 都完成(无论成功还是失败),返回每个 Promise 的结果对象数组。

语法

Promise.allSettled(iterable);

参数

  • iterable: 可迭代对象,每一项为一个 Promise。

使用场景

  1. 批量处理异步任务,并需要查看每个任务的状态,而不是中断整个流程。
  2. 不希望因为一个任务失败而影响其他任务。

示例

const promise1 = Promise.resolve(1);
const promise2 = Promise.reject('Error!');
const promise3 = Promise.resolve(3);

Promise.allSettled([promise1, promise2, promise3]).then((results) =>
  console.log(results)
);
/* 输出:
[
  { status: 'fulfilled', value: 1 },
  { status: 'rejected', reason: 'Error!' },
  { status: 'fulfilled', value: 3 }
]
*/

5.5、Promise.race

功能

Promise.race 接收一个包含多个 Promise 的数组,返回第一个**完成(无论是 Fulfilled 还是 Rejected)**的 Promise。

语法

Promise.race(iterable);

参数

  • iterable: 可迭代对象,每一项为一个 Promise。

使用场景

  1. 超时控制:希望异步任务在一定时间内完成,否则返回超时错误。
  2. 从多个任务中选择最快完成的。

示例

const promise1 = new Promise((resolve) => setTimeout(() => resolve('A'), 100));
const promise2 = new Promise((resolve) => setTimeout(() => resolve('B'), 200));

Promise.race([promise1, promise2]).then((value) => console.log(value)); // 输出: A

超时控制示例

const task = new Promise((resolve) => setTimeout(() => resolve('Done'), 500));
const timeout = new Promise((_, reject) => setTimeout(() => reject('Timeout!'), 300));

Promise.race([task, timeout])
  .then(console.log)
  .catch(console.error); // 输出: Timeout!

5.6、Promise.any

功能

Promise.any 返回第一个 成功(Fulfilled) 的 Promise。

  • 如果所有 Promise 都被 Rejected,则返回一个 Rejected 状态的 Promise,并包含所有拒因的 AggregateError

语法

Promise.any(iterable);

参数

  • iterable: 可迭代对象,每一项为一个 Promise。

使用场景

  1. 在多个任务中选择最快完成且成功的任务。
  2. 希望忽略失败的任务,只处理成功结果。

示例

const promise1 = Promise.reject('Error!');
const promise2 = Promise.resolve('Success!');
const promise3 = Promise.resolve('Another success!');

Promise.any([promise1, promise2, promise3]).then((value) =>
  console.log(value)
);
// 输出: Success!

错误示例

const promise1 = Promise.reject('Error1');
const promise2 = Promise.reject('Error2');

Promise.any([promise1, promise2]).catch((error) => console.log(error));
// 输出: AggregateError: All promises were rejected

5.7、总结

方法功能概述成功返回失败返回
Promise.resolve创建一个 Fulfilled 状态的 Promisevalue-
Promise.reject创建一个 Rejected 状态的 Promise-reason
Promise.all所有 Promise 成功时返回结果数组,任意一个失败则直接返回该失败的 Promise结果数组第一个失败的 Promise
Promise.allSettled等待所有 Promise 完成,无论成功或失败包含状态和值/原因的结果对象数组永远不会失败
Promise.race返回第一个完成(成功或失败)的 Promise第一个完成的结果第一个完成的错误
Promise.any返回第一个成功的 Promise,所有失败时返回 AggregateError第一个成功的结果AggregateError(包含所有失败的原因)