js需要同时发起百条接口请求怎么办?--通过Promise实现分批处理接口请求

如何通过 Promise 实现百条接口请求?
实际项目中遇到需要批量发起上百条接口请求怎么办?

前言

  • 不知你项目中有没有遇到过这样的情况,反正我的实际工作项目中真的遇到了这种玩意,一个接口获取一份列表,列表中的每一项都有一个属性需要通过另一个请求来逐一赋值,然后就有了这份封装
  • 真的是很多功能都是被逼出来的
  • 这份功能中要提醒一下:批量请求最关键的除了分批功能之外,适当得取消任务和继续任务也很重要,比如用户到了这个页面后,正在发起百条数据请求,但是这些批量请求还没完全执行完,用户离开了这个页面,此时就需要取消剩下正在发起的请求了,而且如果你像我的遇到的项目一样,页面还会被缓存,那么为了避免用户回到这个页面,所有请求又重新发起一遍的话,就需要实现继续任务的功能,其实这个继续任务比断点续传简单多了,就是过滤到那些已经赋值的数据项就行了
  • 如果看我啰啰嗦嗦一堆烂东西没看明白的话,就直接看下面的源码吧

源码在此!

  • 【注】:这里的 httpRequest 请根据自己项目而定,比如我的项目是uniapp,里面的http请求是 uni.request,若你的项目是 axios 或者 ajax,那就根据它们来对 BatchHttp 中的某些部分进行相应的修改

    • 比如:其中的 cancelAll() 函数,若你的 http 取消请求的方式不同,那么这里取消请求的功能就需要相应的修改,若你使用的是 fetch 请求,那除了修改 cancelAll 功能之外,singleRequest 中收集请求任务的方式也要修改,因为 fetch 是不可取消的,需要借助 AbortController 来实现取消请求的功能,
    • 提示一下,不管你用的是什么请求框架,你都可以自己二次封装一个 request.js,功能就仿照 axios 这种,返回的对象中包含一个 abort() 函数即可,那么这份 BatchHttp 也就能适用啦
  • BatchHttp.js

js 复制代码
// 注:这里的 httpRequest 请根据自己项目而定,比如我的项目是uniapp,里面的http请求是 uni.request,若你的项目是 axios 或者 ajax,那就根据它们来对 BatchHttp 中的某些部分
import httpRequest from './httpRequest.js'

/**
 * 批量请求封装
 */
export class BatchHttp {

    /** 
     * 构造函数 
     * @param {Object} http - http请求对象(该http请求拦截器里切勿带有任何有关ui的功能,比如加载对话框、弹窗提示框之类),用于发起请求,该http请求对象必须满足:返回一个包含取消请求函数的对象,因为在 this.cancelAll() 函数中会使用到
     * @param {string} [passFlagProp=null] - 用于识别是否忽略某些数据项的字段名(借此可实现"继续上一次完成的批量请求");如:passFlagProp='url' 时,在执行 exec 时,会过滤掉 items['url'] 不为空的数据,借此可以实现"继续上一次完成的批量请求",避免每次都重复所有请求
     */ 
    constructor(http=httpRequest, passFlagProp=null) {
        /** @private @type {Object[]} 请求任务数组 */
        this.resTasks = []
        /** @private @type {Object} uni.request对象 */
        this.http = http
        /** @private @type {boolean} 取消请求标志 */
        this.canceled = false
        /** @private @type {string|null} 识别跳过数据的属性 */
        this.passFlagProp = passFlagProp
    }


    /**
     * 将数组拆分成多个 size 长度的小数组
     * 常用于批量处理控制并发等场景
     * @param {Array} array - 需要拆分的数组
     * @param {number} size - 每个小数组的长度
     * @returns {Array} - 拆分后的小数组组成的二维数组
    */
    #chunk(array, size) {
        const chunks = []
        let index = 0
    
        while(index < array.length) {
        chunks.push(array.slice(index, size + index))
        index += size;
        }
    
        return chunks
    }

    /** 
     * 单个数据项请求 
     * @private
     * @param {Object} reqOptions - 请求配置
     * @param {Object} item - 数据项 
     * @returns {Promise} 请求Promise
    */
    #singleRequest(reqOptions, item) {
        return new Promise((resolve, _reject) => {
            const task = this.http({
                url: reqOptions.url, 
                method: reqOptions.method || 'GET',
                data: reqOptions.data,
                success: res => {
                    resolve({sourceItem:item, res})
                }
            })
            this.resTasks.push(task)
        })
    }

    /**
     * 批量请求控制
     * @private
     * @async
     * @param {Object} options - 函数参数项
     * @param {Array} options.items - 数据项数组
     * @param {Object} options.reqOptions - 请求配置  
     * @param {number} [options.concurrentNum=10] - 并发数
     * @param {Function} [options.chunkCallback] - 分块回调 
     * @returns {Promise}
    */
    async #batchRequest({items, reqOptions, concurrentNum = 10, chunkCallback=(ress)=>{}}) {
        const promiseArray = []
        let data = []
        const passFlagProp = this.passFlagProp
        if(!passFlagProp) {
            data = items
        } else {
            // 若设置独立 passFlagProp 值,则筛选出对应属性值为空的数据(避免每次都重复请求所有数据,实现"继续未完成的批量请求任务")
            data = items.filter(d => !Object.hasOwnProperty.call(d, passFlagProp) || !d[passFlagProp])
        }
        // --
        if(data.length === 0) return

        data.forEach(item => {
            const requestPromise = this.#singleRequest(reqOptions, item)
            promiseArray.push(requestPromise)
        })

        const promiseChunks = this.#chunk(promiseArray, concurrentNum) // 切分成 n 个请求为一组

        for (let ck of promiseChunks) {
            // 若当前处于取消请求状态,则直接跳出
            if(this.canceled) break
            // 发起一组请求
            const ckRess = await Promise.all(ck) // 控制并发数
            chunkCallback(ckRess) // 每完成组请求,都进行回调
        }
    }

    /**
     * 设置用于识别忽略数据项的字段名
     * (借此参数可实现"继续上一次完成的批量请求");
     * 如:passFlagProp='url' 时,在执行 exec 时,会过滤掉 items['url'] 不为空的数据,借此可以实现"继续上一次完成的批量请求",避免每次都重复所有请求
     * @param {string} val 
     */
    setPassFlagProp(val) {
        this.passFlagProp = val
    }

    /**
     * 执行批量请求操作
     * @param {Object} options - 函数参数项
     * @param {Array} options.items - 数据项数组
     * @param {Object} options.reqOptions - 请求配置  
     * @param {number} [options.concurrentNum=10] - 并发数
     * @param {Function} [options.chunkCallback] - 分块回调 
     */
    exec(options) {
        this.canceled = false
        this.#batchRequest(options)
    }

    /**
     * 取消所有请求任务
     */
    cancelAll() {
        this.canceled = true
        for(const task of this.resTasks) {
            task.abort()
        }
        this.resTasks = []
    }
}
相关推荐
y先森4 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy4 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189114 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿6 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡7 小时前
commitlint校验git提交信息
前端
虾球xz7 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇7 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒7 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员7 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐7 小时前
前端图像处理(一)
前端