likes
comments
collection
share

关于请求这件小事> 作者:江苏苏 ## 一、引言 在日常开发过程中,经常与服务器进行交互。但其实,看似简单的请求,也有一

作者站长头像
站长
· 阅读数 21

作者:江苏苏

一、引言

在日常开发过程中,经常与服务器进行交互。但其实,看似简单的请求,也有一些可以思考的点。大家都知道,浏览器是有并发限制的,最大并发数量是6-8个。那么,引出我们的第一个问题,针对一些相似请求,怎么合并请求?

二、案例

1. 相似请求合并请求

1.1. 背景

最近在梳理登录功能的过程中,发现页面中会多次调用usePromission这个接口来判断当前登录人是否有某个功能的权限。但是,如果页面中有多个功能点需要判断权限,那我们就要多次调用usePromission,也就意味着我们要发起多个相似的请求。但实际上,在实际应用中,针对一些复杂的应用,功能权限点可能很多,再加上页面的一些本身的查询,我们就有可能会超出浏览器的并发限制。所以,针对需要判断是否有某项功能的权限点,我们需要合并请求。大致流程如下:

关于请求这件小事> 作者:江苏苏 ## 一、引言 在日常开发过程中,经常与服务器进行交互。但其实,看似简单的请求,也有一

1.2. 实现:相似请求合并请求

  • 模拟请求
function mockRequest(params) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(
        params.map((p) => {
          return {
            code: p,
            isAllow: Math.random() > 0.5,
          };
        })
      );
    }, 1000);
  });
}
  • 其余代码
// 使用 Promise.resolve() 创建一个 promise 实例,我们用它将一个任务添加到微任务队列
let p = Promise.resolve();
let isFlushing = false;
let params = new Set();
function execute() {
  if (isFlushing) return;
  isFlushing = true;
  p = p.then(() => {
    return mockRequest([...params]);
  });
}

function print(param) {
  params.add(param);关于请求这件小事
  execute();
  return p.then((data) => {
    console.loh('data', data);
    return data.find((item) => item.code === param).isAllow;
  });
}

function test() {
  for (let i = 0; i < 10; i++) {
    print(i).then((isAllow) => {
      console.log(isAllow);
    });
  }
}
test();

执行顺序如下:

  • i = 0,执行print方法,将参数塞入全局的params中;执行execute方法,设置全局的isFlushing为true,同时,将promise pending赋值给p。继续往下执行
  • i = 1,执行print方法,将参数塞入全局的params中;执行execute方法,此时isFlushing为true,直接返回。继续往下执行
  • i = 2,3,4,5,6,7,8,9 同i = 1
  • i = 10,i < 10,循环结束。开始执行微任务 p.then 并将结果返回。此时 17行的data打印的结果如下

关于请求这件小事> 作者:江苏苏 ## 一、引言 在日常开发过程中,经常与服务器进行交互。但其实,看似简单的请求,也有一

2. 不同请求,展示最新的结果

2.1. 背景

在业务开发中,也曾遇到过,列表页通过tab区分,调用不同的接口展示不同的数据。在快速切换的情况下,期望展示的数据是最后一次请求的数据,但事实上,接口请求的时间是不确定的,所以,也会存在在点完未完成,立刻马上点已完成,如果此时,已完成的数据返回的比未完成的更快,那其实页面中展示的数据就是未完成的,但tab的高亮又是已完成的。就会造成数据不对。当然,解决这个问题的方式有很多种,那如果从请求入手,怎么实现不同请求,展示最新的结果呢?

关于请求这件小事> 作者:江苏苏 ## 一、引言 在日常开发过程中,经常与服务器进行交互。但其实,看似简单的请求,也有一

2.2. 实现:不同请求,展示最新的结果

2.3. 例子

模拟:页面中存在两个按钮(点击获取3条数据、点击获取6条数据),我们先点获取6条数据,然后再快速点获取3条数据,通过模拟数据,设置获取6条数据更慢,然后发现,页面展示的是6条数据。按理说应该展示我最新获取的数据三条数据。

import { Button } from "antd";
import { useRef, useState } from "react";

const ClickCount = () => {
  const [data, setData] = useState([]);
  const fetch = (num) => {
    // 模拟接口返回,为了明显看到效果,在这获取6条数据比获取3条数据慢
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(Array(num).fill({}).map((item, index) => {
          return {
            index
          }
        }))
      }, num * 500);
    })
  }

  const getData = (num) => {
    fetch(num).then((data) => {
      setData(data);  
    })
  }
  return (
    <div>
      <Button onClick={() => {getData(3)}}>点击获取3条数据</Button>
      <Button onClick={() => {getData(6)}}>点击获取6条数据</Button>
      {
        data.map(({ index }) => {
          return <div key={index}>渲染的数据{index + 1}</div>
        })
      }
    </div>
  );
};
export default ClickCount;

2.4. 解决方案

只有当当前请求是最新请求时,结果才返回,否则结果就不返回。那怎么知道我当前的返回是最新一次的返回呢?

可以通过useRef。具体如下:

1.全局存储当前请求次数 const requestPool = useRef(0);

2.每次请求+1:

  • requestPool.current += 1; // 每次请求加1
  • const requestId = requestPool.current; // 记录当前是第几次请求

3.请求完成后,比较当前是否时最新返回:

  • requestId === requestPool.current
import { Button } from "antd";
import { useRef, useState } from "react";

const ClickCount = () => {
  const [data, setData] = useState([]);
  const requestPool = useRef(0);  // 记录最新请求
  const fetch = (num) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(Array(num).fill({}).map((item, index) => {
          return {
            index
          }
        }))
      }, num * 500);
    })
  }

  const getData = (num) => {
    requestPool.current += 1;  // 每次请求加1
    const requestId = requestPool.current;  // 记录当前是第几次请求
    fetch(num).then((data) => {
      console.log('data', data)
      if (requestId === requestPool.current) { // 只有当当前请求时最新请求时,才返回
        setData(data);
      }    
    })
  }
  return (
    <div>
      <Button onClick={() => {getData(3)}}>点击返回3条数据</Button>
      <Button onClick={() => {getData(6)}}>点击返回6条数据</Button>
      {
        data.map(({ index }) => {
          return <div key={index}>渲染的数据{index + 1}</div>
        })
      }
    </div>
  );
};
export default ClickCount;

页面效果

关于请求这件小事> 作者:江苏苏 ## 一、引言 在日常开发过程中,经常与服务器进行交互。但其实,看似简单的请求,也有一

就算返回6条数据更慢,但页面只会展示最后一次请求获取的数据。

3. 不同请求,并发请求

3.1. 背景

尽管业务中不常见,但也可以考虑一下不同请求的并发的情况。例如:页面复杂,初始时加载多个接口,再例如下载中心下载多个不同的文件等,都需要考虑浏览器的并发限制。

3.2. 实现:不同请求,并发

3.3. 例子

模拟:页面中存在一个按钮(测试),点击触发6个模拟请求,限制同时并发2个。

import { Button } from "antd"

const Demo = () => {
  // 模拟请求
  const fetch = (num) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log('num', num);
        resolve(num)
      // 注意:此处为了模拟接口的快慢,num为1、3、5时接口返回为1s,2、4、6 时接口返回是3s
      }, 1000 + (num % 2 === 0 ? 2000 : 0));
    })
  }
  const task1 = () => {
    return fetch(1);
  }
  const task2 = () => {
    return fetch(2)
  }
  const task3 = () => {
    return fetch(3);
  }
  const task4 = () => {
    return fetch(4)
  }
  const task5 = () => {
    return fetch(5);
  }
  const task6 = () => {
    return fetch(6)
  }
  // 一次点击,触发6个请求
  const test = async () => {
    task1();
    task2();
    task3();
    task4();
    task5();
    task6();
  }
  
  return (
    <Button onClick={test}>测试</Button>
  )
}
export default Demo;

此时,执行顺序为1s后打印1,3,5。再等2s后打印2、4、6

关于请求这件小事> 作者:江苏苏 ## 一、引言 在日常开发过程中,经常与服务器进行交互。但其实,看似简单的请求,也有一

那怎么实现并发请求数量为2的请求呢?

3.4. 解决方案

每次最多并发2个请求,请求完成了一个,那就继续发起下一个请求。基于此:

1.定义以下几个参数:

  • activeCount 当前正在请求的个数
  • result 并发返回的结果。这里需要注意,接口的返回是有快慢的,所以需要保证接口的返回跟请求的先后对应的上哦

2.初始,当前正在请求的个数小于默认并发的最大个数,发起请求;

3.发起请求后,正在请求的个数+1,只要请求完成了,请求的个数就-1:

  • 判断是否有下一个请求,存在下个请求,继续发起请求
  • 判断请求是否全都结束,全部结束,将结果返回

代码如下:

import { Button } from "antd"

const Demo = () => {
  const fetch = (num) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(num)
      }, 1000 + (num % 2 === 0 ? 2000 : 0));
    })
  }

  // concurrency 默认并发的最大个数
  const requestQueue = (concurrency) => {
    return (urls) => {
      let result = []; // 存储所有请求的结果
      let curIndex = 0; // 请求的下标
      let activeCount = 0; // 当前活跃请求的计数
      return new Promise((resolve, reject) => {
        const dequeue  = (url, index) => {
          activeCount++;
          url().then((data) => {
            result[index] = data;
          }).finally(() => {
            activeCount--
            if (curIndex < urls.length) {
              // url未请求完,继续下一个请求
              dequeue(urls[curIndex], curIndex++)
            }
            if (activeCount === 0) {
              // 无获取的请求,表明所有请求都已结束,返回结果
              resolve(result);
            }
          })
        }
        while (curIndex < urls.length && activeCount < concurrency) {
          dequeue(urls[curIndex], curIndex);
        }
      })
    }
  } 
  const enqueue = requestQueue(2);
  const task1 = () => {
    return fetch(1);
  }
  const task2 = () => {
    return fetch(2)
  }
  const task3 = () => {
    return fetch(3);
  }
  const task4 = () => {
    return fetch(4)
  }
  const task5 = () => {
    return fetch(5);
  }
  const task6 = () => {
    return fetch(6)
  }
  const test = async () => {
  const data = await enqueue([task1, task2, task3, task4, task5, task6])
  console.log('data', data);
  }
  
  return (
    <Button onClick={test}>测试</Button>
  )
}
export default Demo;

此时,执行顺序是每隔1s分别出现 1、3、2、5、4。最后等待2s出现6。然后打印最终的返回结果【1、2、3、4、5、6】

关于请求这件小事> 作者:江苏苏 ## 一、引言 在日常开发过程中,经常与服务器进行交互。但其实,看似简单的请求,也有一

不重要的解析:

  1. 初始,同时发起task1 & task2,task1的返回时间是1s,task2的返回时间3s
  2. 等待1s,task1执行完之后,打印出1;task3开始执行,此时task2还剩2s,task3的返回时间1s
  3. 等待1s,task3执行完之后,打印出3;task4开始执行,此时task2还剩1s,task4返回时间3s
  4. 等待1s,task2执行完之后,打印出2;task5开始执行,此时task4还剩2s,task5返回时间1s
  5. 等待1s,task5执行完之后,打印出5;task6开始执行,此时task4还剩1s,task6返回时间3s
  6. 等待1s,task4执行完之后,打印出4;
  7. 等待2s,task6执行完之后,打印出6;

三、总结

关于请求这件事,一个很小的切入点,但仔细想想,也是有一些值得思考的点存在。相似请求合并请求,不同请求并发请求,不同请求展示最新结果等等等。关于请求,当然远不止于这些。还有很多,但由于篇幅(我累了)原因,就到这吧。最后一句话:知识点无处不在,勤于思考,勤能补拙,天道酬勤。共勉~

转载自:https://juejin.cn/post/7406547615091802122
评论
请登录