如何合并同一接口的相同参数的请求

文章原文地址:如何合并同一接口的相同参数的请求 - 蚊子的前端博客

这两天遇到一个场景,需要把中文的单位名称传给接口,获取对应的 id。但数据是从 excel 中读取的,有批量的数据需要处理,同时又基本都是同一个单位的,但偶尔可能会有其他单位的名称(比如该公司有子公司或者二级公司之类的)。

对于批量数据中都是同单位的名称,没必要每次都去请求接口,可以把相同公司的名字合并到一起,只发起一次接口请求即可。

解决方案的关键在于:如何把相同数据的请求缓存起来,然后在接口完成后触发。

在 JavaScript 中,我们可以利用Promise的特性:

只有 Promise 被 resolve() 的时候,才会执行 then()

这样,我们就可以利用 Promise 的特性,把相同参数的请求先放到数组中缓存起来,然后在接口完成后再触发。

1. 一个简单的例子

我们看个简单的例子:

let firstResolve = () => {};

const secondPromise = new Promise((resolve) => {
  firstResolve = resolve;
});
secondPromise.then(() => {
  console.log("secondPromise fullfilled");
});

setTimeout(() => {
  firstResolve();
}, 2000);

secondPromise 的then()方法什么时候执行,取决于 firstResolve() 的调用时机。

2. 再一个简单的例子

上面的例子很基础,可能还没太了解到多个请求如何得到响应。这里我们暂时不考虑参数是否相同的问题。

/**
 * 合并多个请求,apiCall表示请求的接口,需要返回一个Promise
 */
const mergeRequests = (apiCall) => {
  const queue = []; // 缓存多个请求

  return (...args) => {
    if (!queue.length) {
      // 还没有任何请求,那就发起请求
      const promise = apiCall(...args);

      // 待请求完毕后,执行所有创建的 Promise
      promise.then((response) => {
        queue.forEach((resolve) => resolve(response));
      });
    }

    /**
     * 已经有请求在执行了,那就先创建一个Promise(),
     * 这个 Promise 的`then()`什么时候执行,取决于 resolve() 的调用时机
     */
    return new Promise((resolve) => {
      queue.push(resolve);
    });
  };
};

执行:

const apiCall = (data) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("假装发起了一个请求", Date.now());
      resolve(`Response with ${JSON.stringify(data)}`);
    }, 600);
  });
};

const fn = mergeRequests(apiCall);

// 发起多个相同的请求
fn({ name: "蚊子的前端博客" }).then(console.log);
fn({ name: "蚊子的前端博客" }).then(console.log);
fn({ name: "蚊子的前端博客" }).then(console.log);

从调用结果中,我们可以看到,只有第一个请求发起了,后面的请求都返回了一个 Promise,当第一个请求完成后,会触发所有创建的 Promise 的then()方法。

3. 完整版

我们在上面已经可以合并请求了,但为了方便,没有区分参数,这里我们把参数的要素也加进来。

/**
 * 合并多个请求
 * @param {Promise} apiCall 表示请求的接口
 */
const mergeRequests = (apiCall) => {
  const requestQueue = new Map(); // 存储请求的队列,键是参数,值是 resolve 的数组

  return (...args) => {
    const key = JSON.stringify(args); // 使用参数作为键
    let promiseResolvers = requestQueue.get(key);

    if (!promiseResolvers) {
      // 如果没有队列,创建一个新的请求
      const promise = apiCall(...args);
      promiseResolvers = [];
      promise
        .then((response) => {
          promiseResolvers.forEach((resolve) => resolve(response));

          // 该参数对应的请求全部完成,清除key
          requestQueue.delete(key);
        })
        .catch(() => {
          // 请求失败,清除key
          requestQueue.delete(key);
        });
      requestQueue.set(key, promiseResolvers);
    }

    // 返回一个新的promise,它将在原始请求完成时解决
    return new Promise((resolve) => {
      promiseResolvers.push(resolve);
    });
  };
};

执行:

const apiCall = (data) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("假装发起了一个请求", data);
      resolve(`Response with ${JSON.stringify(data)}`);
    }, 700);
  });
};

const fn = mergeRequests(apiCall);

// 发起多个相同的请求
fn({ name: "蚊子的前端博客" }).then(console.log);
fn({ name: "蚊子的前端博客" }).then(console.log);
fn({ url: "https://www.xiabingbao.com" }).then(console.log);
fn({ name: "蚊子的前端博客" }).then(console.log);
fn({ url: "https://www.xiabingbao.com" }).then(console.log);
fn({ wechat: "前端小茶馆" }).then(console.log);
fn({ wechat: "前端小茶馆" }).then(console.log);

执行结果:

合并相同参数后的请求

从执行结果上可以看出,不同参数发起了不同的请求,而相同参数的请求,则只发起了一次。

欢迎关注我的号:前端小茶馆
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值