前端基于 axios 实现批量任务调度管理器 demo

一、背景介绍

这是一个基于 axios 实现的批量任务调度管理器的 demo。它使用了axios、promise 等多种技术和原理来实现批量处理多个异步请求,并确保所有请求都能正确处理并报告其状态。

假设有一个场景:有一个任务列表,有单个任务的处理功能,但是用户提出需要增加批量处理任务的需求,那么如果 20 个任务,30 个任务,我们显然可以集成在一次请求里,无非就是服务器处理的压力变大,时间慢一点,但是如果越来越多的任务,用户要等待很久,后端为支持这些,只能改成异步处理,等待时间就很不可控了。并且在这个过程中,很有可能对正在排队的任务进行其他处理,会发生一些不必要的“错误”。所以提出前端对这些任务“批量处理”,并且对这些任务处理时可以先对任务进行过滤和去重等,统计并发请求的状态并展示,最后实现了一个简单的【批量任务调度管理器 demo】。

这个功能还略显青涩,所以后续又进行了优化和完善,最近有新的想法,借助第三方库,比如使用类似p-limit或 p-queue 的任务队列库来管理批量任务。这些库提供了更高级的功能,如任务调度、重试、延迟执行等,这些待实验。

二、功能介绍

这个 demo 实现了以下功能:

  1. 批量处理:用户可以一次性提交多个任务,这些任务会被批量处理,将请求任务分成固定大小(groupSize)的批次进行处理,以避免同时发送过多请求导致服务器压力过大或浏览器资源耗尽。
  2. 并发控制:通过递归调用 requestFunction 和控制 groupSize,实现了对并发请求数量的控制。在每次请求完成后再启动新的请求,确保同时运行的请求数量保持在合理范围内。(有优化
  3. 过滤和去重:使用 filter 和 findIndex 方法,对 tableData 进行过滤和去重,确保每个任务只被处理一次,也可以自定义其他处理方式。
  4. 状态管理:计数(successCount 和 errorCount)处理结果,使用 ElMessage 和 ElNotification 进行用户提示,反馈批量操作的结果。
  5. 异步操作和错误处理:通过 axios 库发送异步 HTTP 请求,使用 then, catch 和 finally 方法处理请求结果和错误,并在出错时记录错误信息。(有优化

三、功能代码

// 批量处理多个任务
const batchOperation = (title, operationType, axiosConfig, shouldFilterList) => {
  startLoading() // 可忽略,自定义的加载动画

  const groupSize = 5 // 分组,每组五个请求
  let successCount = 0 // 成功的请求
  let errorCount = 0 // 失败的请求
  let errorMessages = [] // 请求失败时的错误信息记录

  // 过滤得到需要发生请求的任务列表
  const requestTaskList = shouldFilterList
    ? tableData.value.filter((item, index, arr) => arr.findIndex((val) => val.id == item.id) == index)
    : tableData.value

  if (requestTaskList.length == 0) {
    ElMessage.error('没有可操作的任务!')
    stopLoading()
    return
  }

  // 处理请求后的成功或者失败
  const handleResponse = (error) => {
    if (error) {
      errorCount++
      errorMessages.push(error)
    } else {
      successCount++
    }
    if (successCount + errorCount === requestTaskList.length) {
      stopLoading()
      const message =
        errorCount === 0
          ? `${requestTaskList.length} 个任务,全部处理成功。`
          : `${requestTaskList.length} 个任务,${successCount} 个处理成功,${errorCount} 个处理失败。`
      ElNotification({
        title: `${title}结果`,
        message,
        type: errorCount === 0 ? 'success' : 'warning'
      })
      if (errorCount > 0) {
        console.error('处理失败的任务:', errorMessages)
      }
    }
  }
  //发送请求--借助递归
  const requestFunction = () => {
    // isUnmount是控制是否卸载当前组件
    if (isUnmount.value || nowIndex >= requestTaskList.length) {
      return
    }
    const row = requestTaskList[nowIndex++]
    const params = { ...row } // 自定义传参
    axios
      .request({ url: axiosConfig.url, method: axiosConfig.method, params })
      .then((res) => {
        if (res.data.code == 200) {
          handleResponse(null)
        } else {
          handleResponse(res.data.message)
        }
      })
      .catch(handleResponse)
      .finally(requestFunction)
  }

  let nowIndex = 0
  for (let i = 0; i < groupSize; i++) {
    requestFunction()
  }
}

// 批量处理
const batchAll = () => {
  batchOperation(
    '标题',
    'batch', //处理参数的标识
    {
      url: '/batch-task',
      method: 'get',
      params: { task_id: '', id: '' }
    },
    true // 是否过滤列表
  )
}

// 可忽略
const startLoading = () => {
  // 显示加载动画或状态
  containerLoading.value = true
}

const stopLoading = () => {
  // 隐藏加载动画或状态
  containerLoading.value = false
}

// 因为是vue,所以在组件卸载之前,取消所有未完成的请求----可忽略
onBeforeUnmount(() => {
  isUnmount.value = true
})

以上功能能实现的是上述阐述的功能,也算一个简单的批量任务调度管理器,但是还有很多优化方向:

  1. 错误信息收集和展示:
    当前的错误处理仅记录了错误计数。可以改进为记录详细的错误信息,并在批量操作完成后展示这些错误信息,帮助用户理解哪些任务失败了以及原因(这个看需求)。
  2. 动态并发控制:
    使用动态并发控制,根据当前系统负载或网络状况调整并发请求数量,进一步优化性能和稳定性(这个需要依靠工具库完成)。
  3. 任务重试机制:
    为失败的请求添加重试机制,例如在某个请求失败后,可以重试一定次数,以提高成功率(这个看需求)。
  4. Promise.all 优化:
    使用 Promise.all 处理每一批次的请求,而不是递归调用 requestFunction,可以使代码更简洁,并减少递归带来的栈深度问题(这个现在就可以优化)。

四、功能优化

现在做的是基于 promise.all 的优化。我们要知道,promise 本来就是 JS 有的一个 API,那么当时我为何不考虑它呢,还是我当时没有考虑到。
Axios 是基于 Ajax 和 Promise 封装,本来就可以利用 Promise 来更好的管控请求回调嵌套造成的回调地狱,如果不加控制,查了一下,那种递归确实可能会造成调用栈过深,出现栈溢出问题(我还没有遇到过,不知道这种情况是什么样的)。

优化前后比较

通过递归调用 requestFunction 来处理任务,这种方式虽然有效,但有以下几个缺点:

1. 递归深度问题:对于大量任务,递归调用可能导致调用栈过深,出现栈溢出问题。
2. 复杂性和可维护性:递归调用使代码较为复杂,逻辑不直观,不容易维护。
3. 难以控制并发:递归调用在控制并发请求数量上不够灵活。

优化后的方式使用了 Promise.all,通过批量处理的方式来提高代码的可读性和可靠性:

1. 避免递归:使用 Promise.all 处理每个批次的请求,避免了递归调用导致的栈深度问题。
2. 简化代码:优化后的代码结构更加清晰,逻辑更加直观,便于维护。
3. 控制并发:批量处理的方式更加灵活,可以方便地控制并发请求的数量。
4. 更好的错误处理:单独处理每个请求的错误,不会因为一个请求失败而导致整个批次停止

优化代码

// 批量处理
const batchOperation = (title, operationType, axiosConfig, shouldFilterList, tabs) => {
  startLoading()

  const groupSize = 5
  let successCount = 0
  let errorCount = 0
  let errorMessages = []

  const requestTaskList = shouldFilterList
    ? tableData.value.filter((item, index, arr) => arr.findIndex((val) => val.id == item.id) == index)
    : tableData.value

  if (requestTaskList.length === 0) {
    ElMessage.error('没有可操作的任务!')
    stopLoading(tabs)
    return
  }

  const handleResponse = () => {
    if (successCount + errorCount === requestTaskList.length) {
      stopLoading(tabs)
      const message =
        errorCount === 0
          ? `${requestTaskList.length} 个任务,全部处理成功。`
          : `${requestTaskList.length} 个任务,${successCount} 个处理成功,${errorCount} 个处理失败。`
      ElNotification({
        title: `${title}结果`,
        message,
        type: errorCount === 0 ? 'success' : 'warning'
      })
      if (errorCount > 0) {
        console.error('处理失败的任务:', errorMessages)
      }
    }
  }

  const makeRequest = (row) => {
    let params = {}

    return axios
      .request({ url: axiosConfig.url, method: axiosConfig.method, params })
      .then((res) => {
        if (res.data.code === 200) {
          successCount++
        } else {
          errorCount++
          errorMessages.push(res.data.message)
        }
      })
      .catch((error) => {
        errorCount++
        errorMessages.push(error.message || error)
      })
  }

  const executeBatch = (batch) => {
    return Promise.all(batch.map(makeRequest)).catch(() => {}) // 忽略批次中的错误,单独处理
  }

  let nowIndex = 0
  const batches = []

  while (nowIndex < requestTaskList.length) {
    const batch = requestTaskList.slice(nowIndex, nowIndex + groupSize)
    batches.push(executeBatch(batch))
    nowIndex += groupSize
  }

  Promise.all(batches).finally(handleResponse)
}

五、总结

两者其实都是并发处理一组又一组的请求,从性能上,我选择 20 个任务,实现的效果,优化后和优化前:
在这里插入图片描述

可以看得出,其实两者没有什么区别,可能因为我的实验数量太少,但是我查阅了资料,得出使用 Promise.all 的优点:

代码简洁:Promise.all 的实现方式较为简单,代码可读性强。
无栈深度问题:Promise.all 没有递归调用的问题,不会导致栈溢出。
就以上两点,也算是有了一点点效果吧。

最后,想试试另一种工具库来实现这个功能,就是借助上面提到的库,未完~

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个实现批量删除的示例代码: ```vue <template> <div> <button @click="deleteSelected">批量删除</button> <table> <thead> <tr> <th><input type="checkbox" v-model="selectAll"></th> <th>ID</th> <th>名称</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="item in items" :key="item.id"> <td><input type="checkbox" v-model="selectedItems" :value="item.id"></td> <td>{{ item.id }}</td> <td>{{ item.name }}</td> <td> <button @click="deleteItem(item.id)">删除</button> </td> </tr> </tbody> </table> </div> </template> <script> import axios from 'axios' export default { data() { return { items: [], selectedItems: [], selectAll: false } }, mounted() { this.fetchData() }, methods: { fetchData() { axios.get('/api/items').then(res => { this.items = res.data }) }, deleteItem(id) { axios.delete(`/api/items/${id}`).then(() => { this.fetchData() }) }, deleteSelected() { axios.delete('/api/items', { data: { ids: this.selectedItems } }).then(() => { this.selectedItems = [] this.selectAll = false this.fetchData() }) } }, watch: { selectAll(val) { if (val) { this.selectedItems = this.items.map(item => item.id) } else { this.selectedItems = [] } } } } </script> ``` 这个示例代码假设你已经有一个 `/api/items` 接口可以获取所有的项,并且可以通过 DELETE 方法来删除一个或多个项。其中,`selectedItems` 是一个数组,存储了当前选中的项的 ID,`selectAll` 是一个布尔值,表示是否选中了所有项。在 `deleteSelected` 方法中,我们使用了 axios 的 `delete` 方法,将选中的项的 ID 作为参数传递给后端。注意,在这里我们使用了 `data` 选项来指定传递的数据,而不是使用 `params` 选项。这是因为在 RESTful API 设计中,使用 DELETE 方法删除资源时,应该将要删除的资源的标识符放在请求的正文中,而不是放在 URL 的参数中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值