Promise.try 和 Promise.withResolvers,你了解多少呢?

1,625 阅读9分钟

该系列是本人准备面试的笔记,或许有描述不当的地方,请在评论区指出,感激不尽。

其他篇章:

  1. 从 babel 编译看 async/await
  2. 挑战ChatGPT提供的全网最复杂“事件循环”面试题
  3. Vue.nextTick 从v3.5.13追溯到v0.7.0
  4. Vue 怎么监听 Set,WeakSet,Map,WeakMap 变化?
  5. Vue 是怎么从<HelloWorld />、<component is='HelloWorld'>找到HelloWorld.vue

前言

最近在准备面试,刚好看到金石计划,所以一拍即合。准备写一系列文章巩固知识点的同时,顺便赚点零花钱,一举两得。

图司机-20241124-40939742.gif

本篇是 Promise 的相关总结,走过路过不要错过,点赞收藏评论,一键三连 [比心]。

Promise 的诞生

众所周知,JavaScript 是一种单线程的语言,依赖事件循环机制来实现异步操作。常见的异步任务包括:

  • 网络请求(如 fetch
  • 文件操作(如 fs 模块)
  • 定时器(如 setTimeout

然而,传统的异步编程模式主要依赖 回调函数(callback),这种模式虽然简单,但在实际应用中暴露出了一些严重的问题。


1. 回调地狱

当多个异步操作需要串行执行时,回调函数会层层嵌套,导致代码可读性极差。代码逻辑简单,却难以阅读和维护。

image.png

注:图片来自es6-深入理解promise,读书人的事,能算偷么?

2. 错误处理复杂

在传统的回调模式中,每个异步任务都需要独立的错误处理机制。稍有不慎,错误就可能被遗漏。

doSomething((err, result) => {
    if (err) {
        handleError(err);
    } else {
        doAnotherThing(result, (err, anotherResult) => {
            if (err) {
                handleError(err);
            }
        });
    }
});

3. 缺乏流程控制能力

回调函数没有提供内置的工具来管理多个异步任务的组合,比如并行处理多个任务并获取其结果。


Promise 的出现解决了这些问题:

  • 它提供了一种更清晰的链式调用方式,减少嵌套。
  • 它内置了错误捕获机制,使得异常处理更方便。
  • 它是状态化的(Pending、Fulfilled、Rejected),避免了重复调用回调的问题。

Promise 的定义以及 Promises/A+ 规范

一个 Promise 是一个代理,它代表一个在创建 promise 时不一定已知的值。它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。这使得异步方法可以像同步方法一样返回值:异步方法不会立即返回最终值,而是返回一个 promise,以便在将来的某个时间点提供该值。引自MDN

image.png

核心思想:

  • 将异步操作的结果通过对象的 状态 表达(Pending、Fulfilled、Rejected),同时状态不可逆,一旦状态从 Pending 转变为 FulfilledRejected,就不可再更改。
  • 支持链式调用,使代码扁平化,摆脱回调地狱。
  • 通过 .catch 方法统一捕获错误。

Promises/A+ 规范

原文链接

对 Promise 行为的标准化定义,目的是为不同的 Promise 实现提供一致性。它规范了 Promise 的核心行为,确保各种库或框架的 Promise 实现能够互相兼容。

核心要点:

  • Promise 必须处于以下三种状态之一:Pending、Fulfilled、Rejected。
  • Promise 必须提供 then 方法来访问其当前或最终的 valuereason,该方法接受两个回调函数,一个onFulfilled 用于 Promise 被兑现时调用,一个 onRejected 用于 Promise 被拒绝时调用。
  • 如果 onFulfilledonRejected 返回了一个值 x,Promise 需要解析 x 的值以决定新的 Promise 的状态。

promises-testsPromises/A+ 规范 的官方测试套件,用于验证一个 Promise 实现是否完全遵循 Promises/A+ 标准。

Promise 相关 API

Promise.resolvePromise.reject 就不过多介绍了。

Promise.all

Promise.all 接受一个包含多个 Promise 的数组,并返回一个新的 Promise。只有当数组中的所有 Promise 都成功时,Promise.all 才会变为 fulfilled 并返回所有结果的数组。如果其中一个 Promise 被拒绝,Promise.all 将立即变为 rejected 并返回第一个被拒绝的原因。

Promise.all = function(promises) {
  return new Promise((resolve, reject) => {
    let results = [];
    let completedCount = 0;

    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(value => {
        results[index] = value;
        completedCount++;
        if (completedCount === promises.length) {
          resolve(results);
        }
      }).catch(reject); // 一旦有一个 Promise 被拒绝,直接 reject
    });
  });
};
  • 批量 API 请求: 获取用户数据和对应的订单数据,然后一起处理。
  • 加载多个资源: 并行加载多张图片或文件,等所有资源加载完成后再渲染页面。

Promise.allSettled

Promise.allSettled 接受一个包含多个 Promise 的数组,并返回一个新的 Promise,其状态总是 fulfilled。返回的值是每个输入 Promise 的结果对象,包含 statusvaluereason

Promise.allSettled = function(promises) {
  return new Promise(resolve => {
    let results = [];
    let completedCount = 0;

    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(value => {
        results[index] = { status: 'fulfilled', value };
      }).catch(reason => {
        results[index] = { status: 'rejected', reason };
      }).finally(() => {
        completedCount++;
        if (completedCount === promises.length) {
          resolve(results);
        }
      });
    });
  });
};
  • 批量任务监控: 执行多个任务(如文件上传),无论成功还是失败,都需要统计结果。
  • 日志记录: 记录多个异步事件的执行结果,用于后续分析。

Promise.any

Promise.any 接受一个包含多个 Promise 的数组,返回一个新的 Promise。只要有一个 Promise 成功,Promise.any 就会变为 fulfilled,并返回成功的值。如果所有 Promise 都被拒绝,则返回一个 AggregateError

Promise.any = function(promises) {
  return new Promise((resolve, reject) => {
    let rejections = [];
    let rejectedCount = 0;

    promises.forEach(promise => {
      Promise.resolve(promise).then(resolve).catch(reason => {
        rejections.push(reason);
        rejectedCount++;
        if (rejectedCount === promises.length) {
          reject(new AggregateError(rejections, 'All Promises were rejected'));
        }
      });
    });
  });
};
  • 容灾处理: 从多个服务器中请求数据,只要一个服务器返回成功即可。
  • 抢占式策略: 执行多个备用任务,最先成功的即为最终结果。

Promise.race

Promise.race 接受一个包含多个 Promise 的数组,返回一个新的 Promise。一旦其中任何一个 Promise 解决或拒绝,Promise.race 就会解决或拒绝。

Promise.race = function(promises) {
  return new Promise((resolve, reject) => {
    promises.forEach(promise => {
      Promise.resolve(promise).then(resolve).catch(reject);
    });
  });
};
  • 请求超时: 设置一个超时机制,如果 API 请求超时则直接失败。
  • 竞态任务: 两个任务同时执行,选最快完成的结果。

Promise.try

Promise.try 是一个非标准方法,用于确保某段代码以 Promise 的形式运行(不论是否同步)。这是为了统一处理同步和异步逻辑。

Promise.try = function(fn) {
  return new Promise((resolve, reject) => {
    try {
      resolve(fn());
    } catch (error) {
      reject(error);
    }
  });
};
  • 安全调用未知函数: 不确定函数是同步还是异步。
  • 简化控制流: 使用 try-catch 的同时支持异步逻辑。

示例:

function mayBeSyncOrAsync() {
  if (Math.random() > 0.5) {
    return 'Sync value';
  } else {
    return Promise.resolve('Async value');
  }
}

Promise.try(() => mayBeSyncOrAsync())
  .then(result => console.log('Result:', result))
  .catch(error => console.error('Error:', error));

Promise.withResolvers

Promise.withResolvers 是一个实用工具,返回一个对象,包含一个 Promise 和该 Promise 的 resolvereject 方法,方便控制 Promise 的状态。

Promise.withResolvers = function() {
  let resolve, reject;
  const promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
};
  • 事件驱动模型: 在事件发生时触发 Promise 完成。
  • 复杂的异步流程: 手动挂起和恢复任务。

示例:

function asyncOperation() {
  const { promise, resolve, reject } = Promise.withResolvers();

  setTimeout(() => {
    if (Math.random() > 0.5) {
      resolve('Operation succeeded');
    } else {
      reject(new Error('Operation failed'));
    }
  }, 1000);

  return promise;
}

asyncOperation()
  .then(result => console.log(result))
  .catch(error => console.error(error));

原理实现

Don't say too much,面试题常客,直接上代码。

image.png

class MyPromise {
    constructor(executor) {
        // 初始化状态
        this.state = "pending"; // 状态:pending、fulfilled、rejected
        this.value = undefined; // 成功的值
        this.reason = undefined; // 失败的原因
        this.onFulfilledCallbacks = []; // 成功回调队列
        this.onRejectedCallbacks = []; // 失败回调队列

        // 成功的回调
        const resolve = (value) => {
            if (this.state === "pending") {
                setTimeout(() => { // 确保是异步执行
                    this.state = "fulfilled";
                    this.value = value;
                    this.onFulfilledCallbacks.forEach((callback) =>
                        callback(value)
                    );
                });
            }
        };

        // 失败的回调
        const reject = (reason) => {
            if (this.state === "pending") {
                setTimeout(() => {
                    this.state = "rejected";
                    this.reason = reason;
                    this.onRejectedCallbacks.forEach((callback) =>
                        callback(reason)
                    );
                });
            }
        };

        // 捕获 executor 的异常
        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        // 支持值穿透
        onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
        onRejected =
            typeof onRejected === "function"
                ? onRejected
                : (e) => {
                      throw e;
                  };

        // 返回新的 Promise 实现链式调用
        return new MyPromise((resolve, reject) => {
            const fulfilledTask = () => {
                try {
                    const x = onFulfilled(this.value);
                    resolvePromise(x, resolve, reject);
                } catch (error) {
                    reject(error);
                }
            };

            const rejectedTask = () => {
                try {
                    const x = onRejected(this.reason);
                    resolvePromise(x, resolve, reject);
                } catch (error) {
                    reject(error);
                }
            };

            if (this.state === "fulfilled") {
                setTimeout(fulfilledTask);
            } else if (this.state === "rejected") {
                setTimeout(rejectedTask);
            } else if (this.state === "pending") {
                this.onFulfilledCallbacks.push(fulfilledTask);
                this.onRejectedCallbacks.push(rejectedTask);
            }
        });
    }
}

// 处理 then 的返回值 x
function resolvePromise(x, resolve, reject) {
    if (x === resolve) {
        return reject(new TypeError("Circular reference detected"));
    }

    if (x instanceof MyPromise) {
        // 如果返回的是另一个 Promise,则等待其状态改变
        x.then(resolve, reject);
    } else {
        resolve(x);
    }
}

有趣的 Promise 相关项目

以前觉得 Promise 不就 thencatchfinally,有什么难的。直到看见 那个男人 的仓库,才知道 Promise 的水很深,可玩的东西太多了。

以下列举一些我认为有趣的项目:

  • bluebird:一个高性能、功能丰富的 JavaScript Promise 库。它提供了比原生 JavaScript Promises 更多的功能,同时具有极高的性能表现。

    1. 提供了许多额外的静态方法和实例方法,例如 .map, .reduce, .filter, .each,方便进行异步操作的组合和处理。
    2. 内置的 .cancel() 方法,用于支持可取消的 Promises。
    3. .timeout() 方法,用于为异步操作设置超时时间。
  • q:另一个流行的 JavaScript Promise 库,它是最早实现并推广 Promises/A+ 规范的库之一。

    1. defer() 用于创建和控制 Deferred 对象。
    2. 提供 Q.nfcallQ.denodeify,用于将基于 Node.js 回调风格的函数转换为 Promise 风格
  • p-queue:适用于限制异步(或同步)操作的速率。例如,与 REST API 交互时或执行 CPU/内存密集型任务时。

    1. p-queue 允许你设置并发限制(比如最大同时执行的异步任务数),确保你的应用不会超负荷执行任务。可以在启动队列时指定 concurrency 参数,来限制队列中同时执行的任务数。
    2. 当并发任务数量达到上限时,p-queue 会将新任务排入队列,直到有足够的空间可以执行它们。这样可以避免任务堆积导致性能瓶颈。
    3. 提供了优先级队列的功能,允许你为不同的任务设置不同的优先级,使得高优先级的任务先执行。
    4. 支持取消已加入队列的任务(如果任务还没有执行)。
  • promisees:为开发者提供关于 Promise 性能的可视化和统计数据,帮助识别可能存在的性能瓶颈或不良的异步操作实践。

  • p-progress:对于在长时间运行的异步操作期间向用户报告进度。

  • proxymise:允许方法和属性链式操作,无需中间的 then()await,从而获得更简洁的代码。

const proxymise = require('proxymise');

// Instead of thens
foo.then(value => value.bar())
  .then(value => value.baz())
  .then(value => value.qux)
  .then(value => console.log(value));

// Instead of awaits
const value1 = await foo;
const value2 = await value1.bar();
const value3 = await value2.baz();
const value4 = await value3.qux;
console.log(value4);

// Use proxymise
const value = await proxymise(foo).bar().baz().qux;
console.log(value);

* 还有 promise-fun 记录了 sindresorhus 所有 Prumise 相关的模块。

最后

希望这篇文章能带给你新的感悟。说实话,我也是复习的时候才知道 Promise.tryPromise.withResolvers

朋友们,你们遇到的 Promise 面试真题都是什么样子?欢迎在评论区分享。

1-1720751939.jpeg