前端 vue单页面中请求数量过多问题 控制单页面请求并发数

文章讲述了在项目中遇到大量柜子接口请求导致超时的问题,作者通过改进代码实现分批请求并发控制,同时讨论了如何在获取数据过程中实现组件的动态渲染,以及避免接口请求造成的内存泄漏,最后提到懒加载作为更优的解决方案。
摘要由CSDN通过智能技术生成

需求背景:

页面中需要展示柜子,一个柜子需要调用 详情接口以及状态接口

也就是说有一个柜子就需要调用两个接口,在项目初期,接手的公司项目大概也就4-5个柜子,最多的也不超过10个,但是突然进来一个项目,这个项目中有 TM 300多台柜子,所以第一次渲染的时候,同时向服务器请求了600多个接口,导致接口超时,服务也挂了,主要也没想到会存在这种情况,咱也没遇到过啊,ok,既然问题来了,咱就解决问题

我一开始的代码逻辑就是先获取机柜数量,然后循环这个数组,在循环体中调用接口,不过我是通过添加到promise数组中,然后统一处理的,上代码

const getCabinetNum = async (id) => {
	let cabinetList = await cabinetApi.getCabinetList({
		projectId: id ? id : projectId.value
	})
	const promises = cabinetList?.map(async (cabinet) => {
		const data = await cabinetApi.getCabinetLive({
			cabinetId: cabinet.instanceUid,
			projectId: cabinet.projectId
		})
		const status = await cabinetApi.getCabinetStatus({
			cabinetId: cabinet.instanceUid,
			projectId: cabinet.projectId
		})
		return {...data, ...status}
	})
	if (promises && promises.length > 0) {
		const results = await Promise.all(promises)
		cabinetData.value = results
	}

}

cabinetData.value 就是我最后渲染柜子的数据

这段代码的问题有两个:
1. 如果cabinetList 的长度过长 比如20个,我就要同时请求40个接口
2. 我在完全获取 cabinetData 之前,柜子是渲染不出来的,因为我封装的柜子组件是需要循环 cabinetData 去渲染的

那么,先来解决第一个问题,如何控制请求并发数量

思路:将 cabinetList 进行分割成组,控制每组的个数,然后通过组的形式向后端发送请求

const fetchCabinetDetailsInBatches = async (cabinetList, batchSize) => {
	// 计算需要分成的批次数量
	const batches = Math.ceil(cabinetList.length / batchSize)

	// 用于存储所有机柜详情的数组
	const allCabinetData = []

	// 循环每个批次
	for (let i = 0; i < batches; i++) {
		// 获取当前批次的起始索引和结束索引
		const startIndex = i * batchSize
		const endIndex = Math.min((i + 1) * batchSize, cabinetList.length)

		// 获取当前批次的机柜列表
		const batchCabinets = cabinetList.slice(startIndex, endIndex)

		// 定义一个数组,用于存储当前批次的所有 getCabinetLive 方法返回的 Promise 对象
		const promiseList = []
		// 循环遍历当前批次的机柜列表,为每个机柜调用 getCabinetLive,getCabinetStatus 方法,并将返回的 Promise 对象存储到 promiseList 中
		batchCabinets.forEach((cabinet) => {
			promiseList.push(
					Promise.all([
						cabinetApi.getCabinetLive(
							{
								cabinetId: cabinet.instanceUid,
								projectId: cabinet.projectId
							}
						),
						cabinetApi.getCabinetStatus(
							{
								cabinetId: cabinet.instanceUid,
								projectId: cabinet.projectId
							}
						)
					])
				)
		})

		// 使用 Promise.all() 并行处理当前批次的所有 Promise 对象
		const batchResults = await Promise.all(promiseList)
		// 将当前批次的机柜数据存入 allCabinetData 数组中
		batchResults.forEach(([cabinetInfoList, status], index) => {
			allCabinetData[startIndex + index] = {...cabinetInfoList, status}
		})
	}
	return allCabinetData
}

这个函数是用来做分组以及处理数据的

使用如下

const getCabinetNum = async (id) => {
	let cabinetList = await cabinetApi.getCabinetList({
		projectId: id ? id : projectId.value
	})
	cabinetData.value = await fetchCabinetDetailsInBatches(cabinetList, 3)
}

其实主要还是利用promise.all 方法,分批处理每组的多个请求

2.下面解决第二个问题,如何能在完全获取到 cabinetData 之前渲染出柜子,并且,伴随着接口的请求,接着渲染呢

上面我提到了我是通过将 cabinetData 传递给子组件的,那么我只需要要监听 cabinetData 的变化,不断更新 cabinetData 不就好了,我首先想到的是 computed

const renderedCabinets = computed(() => {
	return cabinetData.value
})

通过不断变化的 cabinetData.value 将 renderedCabinets传递给子组件

但是上面的代码存在的问题就是 cabinetData.value还是需要等待全部获取,所以只要稍作修改即可

const fetchCabinetDetailsInBatches = async (cabinetList, batchSize) => {
	// 计算需要分成的批次数量
	const batches = Math.ceil(cabinetList.length / batchSize)

	// 用于存储所有机柜详情的数组
	const allCabinetData = []

	// 循环每个批次
	for (let i = 0; i < batches; i++) {
		// 获取当前批次的起始索引和结束索引
		const startIndex = i * batchSize
		const endIndex = Math.min((i + 1) * batchSize, cabinetList.length)

		// 获取当前批次的机柜列表
		const batchCabinets = cabinetList.slice(startIndex, endIndex)

		// 定义一个数组,用于存储当前批次的所有 getCabinetLive 方法返回的 Promise 对象
		const promiseList = []
		// 循环遍历当前批次的机柜列表,为每个机柜调用 getCabinetLive,getCabinetStatus 方法,并将返回的 Promise 对象存储到 promiseList 中
		batchCabinets.forEach((cabinet) => {
			promiseList.push(
					Promise.all([
						cabinetApi.getCabinetLive(
							{
								cabinetId: cabinet.instanceUid,
								projectId: cabinet.projectId
							}
						),
						cabinetApi.getCabinetStatus(
							{
								cabinetId: cabinet.instanceUid,
								projectId: cabinet.projectId
							}
						)
					])
				)
		})

		// 使用 Promise.all() 并行处理当前批次的所有 Promise 对象
		const batchResults = await Promise.all(promiseList)
		// 将当前批次的机柜数据存入 allCabinetData 数组中
		batchResults.forEach(([cabinetInfoList, status], index) => {
			allCabinetData[startIndex + index] = {...cabinetInfoList, status}
		})
		
		//这里!!!!!!!!!!!!!!!!!
		cabinetData.value = [...allCabinetData]
	}
	return allCabinetData
}

我在每次循环之后 将 cabinetData.value 赋值为最新获取的数据,这样每次循环,cabinetData.value就会改变,从而 renderedCabinets 也会改变

ok 这样就大功告成了

这段代码其实还有一个问题就是如果在接口没有请求完之前,你离开了当前页面,会导致在别的页面下,没请求完的接口依然在请求,会导致内存泄漏,就跟组件销毁前没销毁定时器是一个道理

可以利用axios的关闭请求的方法来解决,后续会更新代码

其实这个问题还有一种更简单高效的解决方案,就是懒加载,在用户滑动查看到新的柜子之前,不请求,滑动到未加载的机柜就请求接口,跟图片懒加载一个道理,这个方案更加简单明了,而且性能肯定比目前这个方案好,后续我也会试一下这个方案

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值