Promise 并发控制请求

124 阅读2分钟

今天用到了tansktack-query进行请求,但是页面有个接口是获取数据的,需要定时更新,这个接口刷新的频率比较快,而且很多组件都会调用这个接口,但是需要对并发进行控制。

之前在面试字节的时候也是经常写这个题,所以印象也比较深。

于是我先搜索了一下,先来一个普通版本,我随便抄的一个

function getData() {
 const limit = 5; // maximum concurrent requests
 const dataUrls = ['https://example.com/data1.json', 
                   'https://example.com/data2.json',
                   'https://example.com/data3.json',
                   'https://example.com/data4.json',
                   'https://example.com/data5.json',
                   'https://example.com/data6.json'];

 let counter = 0;
 const getDataPromise = dataUrl => {
   return new Promise((resolve, reject) => {
     fetch(dataUrl)
       .then(response => {
         counter--;
         resolve(response);
       })
       .catch(error => {
         counter--;
         reject(error);
       });
   });
 };

 const getDataPromises = dataUrls.map(dataUrl => {
   if (counter < limit) {
     counter++;
     return getDataPromise(dataUrl);
   } else {
     return new Promise(resolve => {
       const interval = setInterval(() => {
         if (counter < limit) {
           counter++;
           clearInterval(interval);
           resolve(getDataPromise(dataUrl));
         }
       }, 100);
     });
   }
 });

 Promise.all(getDataPromises)
   .then(responses => {
     for (const response of responses) {
       // handle each response here
     }
   })
   .catch(error => {
     console.error(error);
   });
}

getData();

但是这个时候很巧AI给我提示了一个,我觉得非常有意思,而且对代码完全没有侵入

import { useQuery } from "@tanstack/react-query"
const MAX_CONCURRENT_REQUESTS = 3

let currentRequests = 0
type QueueItem = {
  resolve: (value: any) => void
  reject: (error: any) => void
  request: () => Promise<any>
}
let requestQueue: QueueItem[] = []


const next = () => {
  if (currentRequests < MAX_CONCURRENT_REQUESTS && requestQueue.length > 0) {
    const nextItem = requestQueue.shift()
    if (nextItem) {
      currentRequests++
      const { request, resolve, reject } = nextItem
      request()
        .then(resolve)
        .catch(reject)
        .finally(() => {
          currentRequests--
          next()
        })
    }
  }
}

const queueRequest = (request: () => Promise<any>) => {
  return new Promise((resolve, reject) => {
    if (currentRequests < MAX_CONCURRENT_REQUESTS) {
      currentRequests++
      request()
        .then(resolve)
        .catch(reject)
        .finally(() => {
          currentRequests--
          next()
        })
    } else {
      requestQueue.push({ resolve, reject, request })
    }
  })
}

export const useChartData = (url: string, v: number, c: boolean) => {
  useQuery({
    queryKey: ['chartData', url, v, c],
    queryFn: () => {
      return queueRequest(() => fetch(url).then(res => res.json()))
    },
    enabled: !!url,
  })
}

这个代码让我觉得有意思的点就是用promise的resolve和队列来进行请求的拦截,每次请求都把当前请求的promise放到队列,这样当队列满的时候,会一直进行等待,上一个完成之后就会自动next,在使用的时候只需要,对于任何方法都可以使用

 queueRequest(() => fetch(url).then(res => res.json()))

实现上面的代码也很容易


const dataUrls = ['https://example.com/data1.json',
  'https://example.com/data2.json',
  'https://example.com/data3.json',
  'https://example.com/data4.json',
  'https://example.com/data5.json',
  'https://example.com/data6.json'];

dataUrls.forEach(url => {
  queueRequest(() => fetch(url).then(res => res.json())).then(data => {
    console.log(data)
  })
})

我看到这个代码的时候突然想到之前看的axios源码,里面拦截器也是通过队列存放拦截器的 resolve和reject,然后在外部触发拦截器的完成和失败