首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

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

编程知识
2024年07月16日 19:44

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

前言

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

源码在此!

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

  • 比如:其中的 cancelAll() 函数,若你的 http 取消请求的方式不同,那么这里取消请求的功能就需要相应的修改,若你使用的是 fetch 请求,那除了修改 cancelAll 功能之外,singleRequest 中收集请求任务的方式也要修改,因为 fetch 是不可取消的,需要借助 AbortController 来实现取消请求的功能,

  • 提示一下,不管你用的是什么请求框架,你都可以自己二次封装一个 request.js,功能就仿照 axios 这种,返回的对象中包含一个 abort() 函数即可,那么这份 BatchHttp 也就能适用啦

  • 简单案例测试 -- batch-promise-test.html

      <!DOCTYPE html>
      <html lang="en">
    
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
      </head>
    
      <body>
    
      </body>
      <script>
    
        /**
         * 批量请求封装
         */
        class BatchHttp {
    
      	/** 
      	 * 构造函数 
      	 * */
      	constructor() {
      	}
    
      	/** 
      	 * 单个数据项请求 
      	 * @private
      	 * @param {Object} reqOptions - 请求配置
      	 * @param {Object} item - 数据项 
      	 * @returns {Promise} 请求Promise
      	*/
      	#singleRequest(item) {
      	  return new Promise((resolve, _reject) => {
      		// 模拟异步请求
      		console.log(`发起模拟异步请求 padding...: 【${item}】`)
      		setTimeout(() => {
      		  console.log(`模拟异步请求 success -- 【 ${item}】`)
      		  resolve()
      		}, 200 + Math.random() * 800)
      	  })
      	}
    
      	#chunk(array, size) {
      	  const chunks = []
      	  let index = 0
    
      	  while (index < array.length) {
      		chunks.push(array.slice(index, size + index))
      		index += size
      	  }
    
      	  return chunks
      	}
    
      	/**
      	 * 批量请求控制
      	 * @private
      	 * @async
      	 * @returns {Promise}
      	*/
      	async #batchRequest() {
      	  const promiseArray = []
      	  let data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
    
      	  data.forEach((item, index) => {
      		// 原来的错误逻辑(原来的逻辑,导致所有的 Promise 回调函数都会被直接执行,那么就只有对 response 进行分批的功能了)
      		// const requestPromise = this.#singleRequest(item)
      		// promiseArray.push(requestPromise)
    
      		// -- 修改为:
      		promiseArray.push(index)
      	  })
    
      	  const promiseChunks = this.#chunk(promiseArray, 10) // 切分成 n 个请求为一组
    
      	  let groupIndex = 1
      	  for (let ckg of promiseChunks) {
      		// -- 修改后新增逻辑(在发起一组请求时,收集该组对应的 Promiise 成员)
      		const ck = ckg.map(idx => this.#singleRequest(data[idx]))
      		// 发起一组请求
      		const ckRess = await Promise.all(ck) // 控制并发数
      		console.log(`------ 第${groupIndex}组分批发起完毕 --------`)
      		groupIndex += 1
      	  }
      	}
    
      	/**
      	 * 执行批量请求操作
      	 */
      	exec(options) {
      	  this.#batchRequest(options)
      	}
        }
    
        const batchHttp = new BatchHttp()
        setTimeout(() => {
      	batchHttp.exec()
        }, 2000)
      </script>
    
      </html>
    
  • BatchHttp.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,index) => {
      			// 原来的错误逻辑(原来的逻辑,导致所有的 Promise 回调函数都会被直接执行,那么就只有对 response 进行分批的功能了)
      			// const requestPromise = this.#singleRequest(reqOptions, item)
      			// promiseArray.push(requestPromise)
      			// -- 修改为:这里暂时只记录下想对应的 data 的数组索引,以便分组用,当然这部分有关分组代码还可以进行精简,比如直接使用 data.map进行收集等方式,但是为了与之前错误逻辑形成对比,这篇文章里还是这样写比较完整
      			promiseArray.push(index)
      		})
    
      		const promiseChunks = this.#chunk(promiseArray, concurrentNum) // 切分成 n 个请求为一组
    
      		for (let ckg of promiseChunks) {
      			// -- 修改后新增逻辑(在发起一组请求时,收集该组对应的 Promiise 成员)
      			const ck = ckg.map(idx => this.#singleRequest(data[idx]))
      			// 若当前处于取消请求状态,则直接跳出
      			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 = []
      	}
      }
    

调用案例在此!

  • 由于我的项目是uni-app这种,方便起见,我就直接贴上在 uni-app 的页面 vue 组件中的使用案例

  • 案例代码仅展示关键部分,所以比较粗糙,看懂参考即可

      <template>
      	<view v-for="item of list" :key="item.key">
      		<image :src="item.url"></image>
      	</view>
      	</template>
      	<script>
      	import { BatchHttp } from '@/utils/BatchHttp.js'
    
      	export default {
      		data() {
      			return {
      				isLoaded: false,
      				batchHttpInstance: null,
      				list:[]
      			}
      		},
      		onLoad(options) {
      			this.queryList()
      		},
      		onShow() {
      			// 第一次进页面时,onLoad 和 onShow 都会执行,onLoad 中 getList 已调用 batchQueryUrl,这里仅对缓存页面后再次进入该页面有效
      			if(this.isLoaded) {
      				// 为了实现继续请求上一次可能未完成的批量请求,再次进入该页面时,会检查是否存在未完成的任务,若存在则继续发起批量请求
      				this.batchQueryUrl(this.dataList)
      			}
      			this.isLoaded = true
      		},
      		onHide() {
      			// 页面隐藏时,会直接取消所有批量请求任务,避免占用���源(下次进入该页面会检查未完成的批量请求任务并执行继续功能)
      			this.cancelBatchQueryUrl()
      		},
      		onUnload() {
      			// 页面销毁时,直接取消批量请求任务
      			this.cancelBatchQueryUrl()
      		},
      		onBackPress() {
      			// 路由返回时,直接取消批量请求任务(虽然路由返回也会执行onHide事件,但是无所胃都写上,会判断当前有没有任务的)
      			this.cancelBatchQueryUrl()
      		},
      		methods: {
      			async queryList() {
      				// 接口不方法直接贴的,这里是模拟的列表接口
      				const res = await mockHttpRequest()
      				this.list = res.data
    
      				// 发起批量请求
      				// 用 nextTick 也行,只要确保批量任务在列表dom已挂载完成之后执行即可
      				setTimeout(()=>{this.batchQueryUrl(resData)},0)
      			},
      			/**
      			 * 批量处理图片url的接口请求
      			 * @param {*} data 
      			 */
      			 batchQueryUrl(items) {
      				let batchHttpInstance = this.batchHttpInstance
      				// 判定当前是否有正在执行的批量请求任务,有则直接全部取消即可
      				if(!!batchHttpInstance) {
      					batchHttpInstance.cancelAll()
      					this.batchHttpInstance = null
      					batchHttpInstance = null
      				}
      				// 实例化对象
      				batchHttpInstance = new BatchHttp()
      				// 设置过滤数据的属性名(用于实现继续任务功能)
      				batchHttpInstance.setPassFlagProp('url') // 实现回到该缓存页面是能够继续批量任务的关键一步 <-----
      				const reqOptions = { url: '/api/product/url' }
      				batchHttpInstance.exec({items, reqOptions, chunkCallback:(ress)=>{
      					let newDataList = this.dataList
      					for(const r of ress) {
      						newDataList = newDataList.map(d => d.feId === r['sourceItem'].feId ? {...d,url:r['res'].msg} : d)
      					}
    
      					this.dataList = newDataList
      				}})
    
      				this.batchHttpInstance = batchHttpInstance
      			},
      			/**
      			 * 取消批量请求
      			 */
      			cancelBatchQueryUrl() {
      				if(!!this.batchHttpInstance) {
      					this.batchHttpInstance.cancelAll()
      					this.batchHttpInstance = null
      				}
      			},
      		}
      	}
      	</script>
    
From:https://www.cnblogs.com/xw-01/p/18306078
本文地址: http://www.shuzixingkong.net/article/92
0评论
提交 加载更多评论
其他文章 BigDecimal的精度与刻度
BigDecimal是Java中用于高精度算术运算的类。当您需要精确地处理非常大或非常小的数字时,例如在金融计算中,它特别有用。由于众所周知得原因,Double这种类型在某些情况下会出现丢失精度的问题,所以在需要对较为敏感的数据(比如与金额有关的)进行运算时,我们都会用BigDecimal。但是,用
BigDecimal的精度与刻度 BigDecimal的精度与刻度
共享库soname机制
目录前言共享库版本号共享库命名机制realnamesonamelinkname总结参考文章 前言 在使用第三方库时,我们会发现第三方库会提供一组文件,他们的后缀一般是.so(如libname.so),.so.x和.so.x.y.z。本文讨论他们之间的关系。 共享库版本号 共享库一般会由于修复bug或
共享库soname机制
Doris failed to initialize storage reader. tablet=106408, res=[NOT_IMPLEMENTED_ERROR]to be implemented
Apache Doris 2.3 以下的版本会存在一个 bug,导致数据在合并时存在异常,在后续查询该字段数据时会提示 [1105] [HY000]: errCode = 2, detailMessage = (192.168.15.228)[CANCELLED]failed to initiali
Doris failed to initialize storage reader. tablet=106408, res=[NOT_IMPLEMENTED_ERROR]to be implemented
设计模式-C#实现简单工厂模式
前言 上一篇文章写了如何使用RabbitMQ做个简单的发送邮件项目,然后评论也是比较多,也是准备去学习一下如何确保RabbitMQ的消息可靠性,但是由于时间原因,先来说说设计模式中的简单工厂模式吧! 在了解简单工厂模式之前,我们要知道C#是一款面向对象的高级程序语言。它有3大特性,封装、继承、多态。
设计模式-C#实现简单工厂模式
拯救SQL Server数据库事务日志文件损坏的终极大招
拯救SQL Server数据库事务日志文件损坏的终极大招 在数据库的日常管理中,我们不可避免的会遇到服务器突然断电(没有进行电源冗余),服务器故障或者 SQL Server 服务突然停掉, 头大的是ldf事务日志文件也损毁了,SQL Server服务器起来之后,发现数据库处于&quot;Recove
拯救SQL Server数据库事务日志文件损坏的终极大招 拯救SQL Server数据库事务日志文件损坏的终极大招 拯救SQL Server数据库事务日志文件损坏的终极大招
Django DRF @action 装饰器
@action 装饰器在Django REST Framework (DRF) 中非常有用,它可以帮助你在ViewSet中创建自定义的动作,而不仅仅是依赖标准的CRUD操作(Create, Read, Update, Delete)。以下是 @action 装饰器的一些常见用法: 1. 创建自定义集
OceanBase 金融项目优化案例(union all 改写)
在工单系统上看到有一条sql问题还没解决,工单描述看到压测场景被cpu资源被这条sql打爆,目前影响到项目进度,比较紧急。 直接联系这位同学看看是否需要帮忙。 慢SQL: SELECT task.*, sc01.aab300 bjsjjg, (SELECT sc05.bsc012 FROM sc05
OceanBase 金融项目优化案例(union all 改写) OceanBase 金融项目优化案例(union all 改写) OceanBase 金融项目优化案例(union all 改写)
Microsoft宣布将在开发人员会议上专注于.NET Aspire
2024年7月15日微软宣布,其开发执行团队将在下个月的开发者大会上聚焦于使用 .NET Aspire 的云原生开发,以及结合人工智能的“现代 SQL”在 Microsoft Fabric 中的应用。微软的 Visual Studio LIVE! 2024 大会不仅是一个会议,而是创新、学习和社区庆
Microsoft宣布将在开发人员会议上专注于.NET Aspire Microsoft宣布将在开发人员会议上专注于.NET Aspire