likes
comments
collection
share

javascript中「并发请求」的场景

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

前言

并发请求是面试中的高频问题,同时也是项目中经常会遇到的问题,所以在这里记录一下,希望可以帮助到你。本文主要从以下几个方面进行展开:

  1. 在本地使用 node 创建并启动一个服务模拟项目中真实开发场景
  2. 并发请求场景分析
  3. 并发请求代码实现

创建并启动服务

1. 创建一个 server 文件夹📁,在终端中 npm init -y

javascript中「并发请求」的场景

2. 使用 yarn add express 下载 express 框架

javascript中「并发请求」的场景

3. 修改 package.json,并在 server 目录下创建 app.js 文件
 "scripts": {
    "dev": "nodemon ./app.js"
  },
4. 启动服务 npm run dev ,如果报错:则下载 sudo npm install -g --force nodemon 重新 npm run dev

javascript中「并发请求」的场景

5. 编写 app.js 文件
// 引入express中间件
const express = require('express');

// 创建web服务器
const app = express();

// 跨域处理
app.all("*", (req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*')
  res.header('Access-Control-Allow-Methods', 'GET')
  next()
})

//请求
for (let i = 0; i < 100; i++) {  //最大请求接口数是100个
  app.get('/test' + i, (req, res) => {
    res.send({
      result: `请求成功:请求的接口是第${i}`
    })
  })
}

// 启动服务器监听8000端口
app.listen(8000, () => {
  console.log('启动成功');
})
6. 在根目录下新建一个 index.html 文件用于引入 axios

javascript中「并发请求」的场景

7. 在index.html 文件中使用的是 cdn 的形式引入 axios
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.6/axios.min.js"></script>
<script type="module" src="./index.js"></script>

并发请求场景分析

所谓并发请求,首先需要考虑并发数,并发数就是一次最多可以发送多少个请求,假设现在有 10url 需要发送请求,控制并发数是 3 ,那么就是一次最多可以发送 3 个,第一次时候拿出三个。如下:

javascript中「并发请求」的场景

🤔这里我们需要思考五个问题。

* 第一个问题:如何每次都可以取到三个 URL 去发送请求?

这里我们可以把请求放入到一个函数中,循环三次,当每次循环这个函数的时候就会发送一次请求。

  function runRequest(){
    //发送请求
  }
  

  for (let i = 0; i < 3; i++) {
    runRequest()
  }
* 第二个问题:我怎么知道当前我请求的是第几个?

我们知道请求的地址是一个数组,这里可以指定index ===0,第一次请求的时候拿到的是数组的第一项,后面调用 runRequestindex++,这样每次请求 runRequest 都会依次从数组中取出请求地址进行请求。

  let index = 0  // 用于指定当前请求的那个
  
  async function runRequest() {
    const url = URLS[index]
    index++
    await axios(url)
  }

  for (let i = 0; i < 3; i++) {
    runRequest()
  }
* 第三个问题: 我是等待当前三个都请求完成以后再取出三个请求,还是等待三个中的一个请求完成以后再取出一个进行请求呢?

第一种情况需要等待上一轮的三个请求完成才会进行下一轮的请求,假如其中一个请求耗时比较长,那么仍然需要等待,从响应的时间的角度来说的话,第二种更好,第二种可以保证当前始终是3个请求。所以在上面的基础上我们可以这样做。

  let index = 0  // 用于指定当前请求的那个
  async function runRequest() {
    const url = URLS[index]
    index++
    try {
       await axios(url)
    } catch (error) {
      // 错误处理
    } finally {
      runRequest()  //当前三个中有一个请求完成,调用自己添加一个请求
    }
  }
  
  for (let i = 0; i < 3; i++) {
    runRequest()
  }
* 第四个问题:当前的 URL1,URL2,URL3中请求的结果如何正确的存放在res1,res2,res3中,可以使用 push 吗?

答案是不可以的,因为无法控制他们三个中谁先请求完成,假如 URL2 请求先完成,返回的结果 push 到一个数组中,那个整个请求返回的结果顺序就会发生错误。解决办法是当每一次 index++之前都将 index 保存在一个变量 i 中,当一个请求完成之后它对应的 i 不会发生改变,这样就可以将它存放在对应的位置。

  const Results = []  // 存放请求返回的结果
  let index = 0  // 用于指定当前请求的那个

  // 处理请求
  async function runRequest() {
    let i = index  
    const url = URLS[index]
    index++
    try {
      const res = await axios(url)
      Results[i] = res
    } catch (error) {
      Results[i] = error
    } finally {
      runRequest()
    }
  }
  
  for (let i = 0; i < 3; i++) {
    runRequest()
  }

* 第五个问题:如果并发数量比接口数量大,假如只有5个接口,但是并发数量是10个,怎么处理?
 let minNum = Math.min(URLS.length, maxNum)
  for (let i = 0; i < minNum; i++) {
    runRequest()
  }

并发请求代码实现

这里请求主要有三个文件:分别是index.html,index.jsEruptRequest.js。值得注意的是⚠️,这里的使用的模块化语法,打开 index.html 的时候需要用服务。

javascript中「并发请求」的场景

index.html代码:
<body>
  <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.6/axios.min.js"></script>
  <script type="module" src="./index.js"></script>
</body>
index.js完整代码:
import { dealtRequest } from "./EruptRequest.js";

// 遍历拿到 url 的集合
function getStack(num) {
  const BASE_URL = 'http://localhost:8000/'
  let stacks = []
  for (let i = 0; i < num; i++) {
    stacks.push(BASE_URL + 'test' + i)
  }
  return stacks
}

let URLS = getStack(10)  //请求总接口数
let maxNum = 3  //请求最大并发数

let result = dealtRequest(URLS, maxNum)
console.log(result);
EruptRequest.js完整代码:
//处理并发请求
export function dealtRequest(URLS, maxNum) {
  const Results = []  // 存放请求返回的结果
  let index = 0  // 用于指定当前请求的那个

  // 处理请求
  async function runRequest() {
    //这里要控制 index 的长度,如果index++ 一直增加就会造成死循环
    if (URLS.length === index) return
    let i = index  //
    const url = URLS[index]
    index++
    try {
      const res = await axios(url)
      Results[i] = res
    } catch (error) {
      Results[i] = error
    } finally {
      runRequest()
    }
  }

  // 比较并发数和接口的数量,防止出现只有5个接口,并发数10的情况
  let minNum = Math.min(URLS.length, maxNum)

  for (let i = 0; i < minNum; i++) {
    runRequest()
  }

  // 这个返回的是一个成功的 promise 对象 ,用于存储最后的请求结果
  return new Promise((reslove) => {
    reslove(Results)
  })
}