Record--front-end practical tips: automatically merged network requests

Here I will share with you some of the knowledge I have summarized on the Internet, I hope it will be helpful to everyone

We often encounter a scenario, such as obtaining user information in batches in a list.

 

It would be very stupid if we send dozens of requests to the backend at one time. At this point we have to learn how to use the logic of batch acquisition.

But there is a problem with batch fetching, that is, I need to fetch from the upper layer of user list items, and then distribute the results to the lower layer

The structure at this point is as follows:

const List = () => {
	return itemInfoList.map((info) => <Item info={info} />)
}

In this way, we can easily solve the problems we encounter, because it is the result obtained by an interface.

but! This way of writing is not conducive to maintenance. Because <Item />the dependency of the component is too large, it is a complete object. It is difficult for other components to be reused, and there is a high probability that this component can only be used in one place. So what should I do if I want to reuse it?

That is written as follows

const List = () => {
	return ids.map((id) => <Item id={id} />)
}

Then <Item />the component can obtain the corresponding data by itself according to the id passed in, and then process it by itself.

So don't you encounter the problem at the beginning? If dozens of user components are rendered at a time, then dozens of network requests are sent to the backend at the same time!

At this time, we need to implement a logic to automatically collect and merge network requests that can be merged.

The complete code is as follows:

interface QueueItem<T, R> {
  params: T;
  resolve: (r: R) => void;
  reject: (reason: unknown) => void;
}

/**
 * 创建一个自动合并请求的函数
 * 在一定窗口期内的所有请求都会被合并提交合并发送
 * @param fn 合并后的请求函数
 * @param windowMs 窗口期
 */
export function createAutoMergedRequest<T, R>(
  fn: (mergedParams: T[]) => Promise<R[]>,
  windowMs = 200
): (params: T) => Promise<R> {
  let queue: QueueItem<T, R>[] = [];
  let timer: number | null = null;

  async function submitQueue() {
    timer = null; // 清空计时器以接受后续请求
    const _queue = [...queue];
    queue = []; // 清空队列

    try {
      const list = await fn(_queue.map((q) => q.params));
      _queue.forEach((q1, i) => {
        q1.resolve(list[i]);
      });
    } catch (err) {
      _queue.forEach((q2) => {
        q2.reject(err);
      });
    }
  }

  return (params: T): Promise<R> => {
    if (!timer) {
      // 如果没有开始窗口期,则创建
      timer = window.setTimeout(() => {
        submitQueue();
      }, windowMs);
    }

    return new Promise<R>((resolve, reject) => {
      queue.push({
        params,
        resolve,
        reject,
      });
    });
  };
}

The usage is:

const fetchUserInfo = createAutoMergedRequest<string, UserBaseInfo>(
  async (userIds) => {
    const { data } = await request.post('/api/user/getUserInfoList', {
      userIds,
    });

    return data;
  }
);

fetchUserInfo(1)
fetchUserInfo(2)
fetchUserInfo(3)

Let's read the code next.

Look at the overall structure first. createAutoMergedRequestThe function returns an anonymous function that accepts parameters and returns the result request. But it should be noted that we have defined two generic types Tand R. where the type of the parameter createAutoMergedRequestaccepted is , and the function definition returned is . This is because he will automatically split the result of the request into independent return values ​​and return them to the corresponding call.fn(mergedParams: T[]) => Promise<R[]>(params: T): Promise<R>

Let's look at the returned function body:

if (!timer) {
    // 如果没有开始窗口期,则创建
    timer = window.setTimeout(() => {
      submitQueue();
    }, windowMs);
  }

  return new Promise<R>((resolve, reject) => {
    queue.push({
      params,
      resolve,
      reject,
    });
  });

First judge whether there is a timer timer in the closure, if not, create a timer, and windowMsexecute submitQueuethe method after . We windowMsdefine as a window period, and requests to call this function during this window period will be collected.

然后创建返回一个promise,把参数和promise相关的下一步操作都推到 queue 中。

等到若干次调用后,定时器到时间了,唤起回调执行submitQueue 方法,我们来看看 submitQueue 的操作。

async function submitQueue() {
  timer = null; // 清空计时器以接受后续请求
  const _queue = [...queue];
  queue = []; // 清空队列
  const ret = fn(_queue.map((q) => q.params));

  try {
    const list = await fn(_queue.map((q) => q.params));
    _queue.forEach((q1, i) => {
      q1.resolve(list[i]);
    });
  } catch (err) {
    _queue.forEach((q2) => {
      q2.reject(err);
    });
  }
}

执行前我们会做一些前置工作,清理 timer, 清理 queue 并把队列里的项单独存放起来,防止影响到下一次执行。

然后我们通过 fn(_queue.map((q) => q.params)) 来把队列中的参数拿出来,传给 fn 调用。此时的 fn 就会接收到一个数组。并确保返回的结果也是一个同等大小且一一对应的数据即可。如果请求无误,我们就循环队列,把结果通过队列中记录的 resolve 把结果返回给我们之前创建的promise。

这样我们就实现了一个工具函数,我们可以在一个窗口期内收集到多个网络请求,并把他们汇聚成一个请求发送到后端。后端结果返回回来后,我们再把请求结果拆分分发给独立的调用方。

本文转载于:

https://juejin.cn/post/7259275893796388925

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

Guess you like

Origin blog.csdn.net/qq_40716795/article/details/132179736