在现代 Web 应用中,我们经常会遇到这样的场景:多个组件在页面加载时同时请求相同的数据。如果不加以控制,这些重复请求不仅会浪费网络资源,还会增加服务器负担,甚至可能导致数据不一致的问题。为了解决这一问题,我们可以使用一种称为“单例请求”的技术。本文将深入探讨如何使用 promiseWithResolvers 和单例 API 包装来高效处理重复请求。

什么是 promiseWithResolvers

promiseWithResolvers 是一个实用的工具函数,它允许我们创建一个 Promise,并在外部控制其 resolvereject。它的核心思想是将 Promise 的控制权交给调用者,以便在合适的时机手动触发 Promise 的状态变更。

promiseWithResolvers 的实现

首先,让我们看看 promiseWithResolvers 的实现:

type PromiseState = 'pending' | 'resolved' | 'rejected';

export function promiseWithResolvers<T = unknown>() {
  let resolve: (value: T | PromiseLike<T>) => void;
  let reject: (reason?: any) => void;
  let finallyHandler: (() => void) | null = null;
  let status: PromiseState = 'pending';

  const promise = new Promise<T>((res, rej) => {
    resolve = (value) => {
      status = 'resolved';
      res(value);
      if (finallyHandler) {
        finallyHandler();
      }
    };
    reject = (reason) => {
      status = 'rejected';
      rej(reason);
      if (finallyHandler) {
        finallyHandler();
      }
    };
  }).finally(() => {
    if (finallyHandler) {
      finallyHandler();
    }
  });

  return {
    promise,
    resolve,
    reject,
    finally: (handler: () => void) => {
      finallyHandler = handler;
    },
    getStatus: () => status
  };
}

代码解析

  • 状态管理:通过 status 属性,我们可以跟踪 Promise 的当前状态(pendingresolvedrejected)。
  • 控制权外露resolvereject 方法被外露,使得调用者可以在适当的时机手动触发 Promise 的状态变更。
  • 清理机制:通过 finally 方法,我们可以在 Promise 结束时执行清理操作,确保资源得以释放。

实战:单例 API 包装

为了更好地理解 promiseWithResolvers 的应用场景,让我们来看一个实际例子:如何使用它来实现单例 API 包装,从而避免重复请求。

假设我们有一个获取用户信息的 API getUser,我们希望在任何时候发起的所有 getUser 请求都只会产生一个实际的网络请求,并且所有调用者都能共享同一个结果。

import { Http } from 'path-to-http'; // 假设 Http 是你的 HTTP 库

let singletonPromise: ReturnType<typeof promiseWithResolvers> | null = null;

export function getUser() {
  if (!singletonPromise) {
    singletonPromise = promiseWithResolvers();
    singletonPromise.finally(() => {
      singletonPromise = null; // 请求完成后清理实例
    });

    Http.get(
      '/auth/user',
      {},
    )
      .then((res) => {
        if (res?.data?.userKey) {
          singletonPromise.resolve(res);
        }
      })
      .catch((error) => {
        singletonPromise.reject(error);
      });
  }

  return singletonPromise.promise;
}

代码解析

  • 单例模式:通过 singletonPromise 变量,我们确保在请求完成之前,所有调用都指向同一个 Promise 实例。
  • 请求逻辑:调用 Http.get 进行实际的网络请求,并在成功时解析响应数据,更新日志,最后调用 resolve 方法。
  • 错误处理:在请求失败时,调用 reject 方法。
  • 清理机制:通过 finally 方法,我们在请求完成后清理 singletonPromise,以便下次请求可以创建新的 Promise。

结语

通过 promiseWithResolvers 和单例 API 包装,我们可以高效地处理重复请求,确保在任何时候发起的所有相同请求都只会产生一个实际的网络请求,并且所有调用者都能共享同一个结果。这不仅优化了网络资源的使用,还提高了应用的性能和稳定性。

希望本文能为你提供启发,帮助你在实际项目中更好地处理类似问题。如果你有任何疑问或建议,欢迎在评论区讨论。Happy coding!