提升前端性能:如何优化多个异步请求的执行效率Promise.all()
在现代的前端开发中,异步请求几乎是不可避免的。无论是从服务器获取数据,还是与外部API进行交互,都需要依赖异步操作来提升用户体验。
随着应用的复杂性增加,我们会遇到一个常见的问题:多个异步请求的串行执行可能导致性能瓶颈,进而影响用户体验。
背景
假设你正在开发一个需要获取多个数据源的页面,比如从数据库获取树形结构、部门列表、项目列表、权限数据等。通常情况下,前端会发出多个异步请求去获取这些数据。以下是有问题的代码示例:
第一段代码:使用await
async handleAuthToolInfoPeople() {await this.fetchTreePeople();await this.fetchDeptList();await this.fetchProjectList();await this.fetchBaseList();await this.fetchAuthData();
}
关键点:
在这段代码中,多个 await
操作是串行执行的,意思是每个请求会依次等待上一个请求完成之后才能执行。如果每个请求需要的时间较长,那么整个页面加载的时间就会显著增加,进而影响用户体验。
关键点:
- 没有并行化:这些请求会依次等待上一个请求完成之后才能执行。
- 使用
await
:使用await
关键字来等待每个请求的完成,因此每个请求会按照顺序执行,如果每个请求需要的时间较长,那么整个页面加载的时间就会显著增加。 - 等待:这段代码会确保所有请求完成后再进行后续操作。
结果:
- 异步请求等待:这些请求会启动,会阻止代码继续执行。
- 请求串行的,代码逻辑不清晰:请求是异步的,按照控制流来确保执行顺序或获取结果。
问题:
- 等待请求完成:如果有后续逻辑依赖这些请求的结果,可能会因为等待过长的时间。
第二段代码:没有 await
和 Promise.all()
async handleAuthToolInfoPeople() {this.fetchTreePeople()this.fetchDeptList()this.fetchProjectList()this.fetchBaseList()this.fetchAuthData()
}
关键点:
- 没有并行化:这些请求没有被包装成
Promise.all()
,而是依次调用了每一个异步方法。每个异步方法会启动一个请求,但它们并不会并行执行。 - 没有
await
:没有使用await
关键字来等待每个请求的完成,因此函数调用会立即返回,可能在请求完成之前就退出了。 - 没有等待:这段代码没有确保所有请求完成后再进行后续操作。虽然每个请求会发起,但它们的执行顺序无法控制,且不会等待请求完成后再处理结果。
结果:
- 异步请求不被等待:这些请求会启动,但不会阻止代码继续执行。如果你依赖于请求的结果来执行后续操作(例如将数据更新到 UI),这段代码可能会遇到问题,因为请求结果可能还未返回。
- 请求依然是并行的,但代码逻辑并不清晰:实际上,虽然请求并行发起,但是并没有充分利用
async/await
来确保请求完成后的处理。换句话说,虽然请求是异步的,但没有控制流来确保执行顺序或获取结果。
问题:
- 没有等待请求完成:如果有后续逻辑依赖这些请求的结果,可能会因为请求未完成而出错。
- 代码逻辑不清晰:缺乏
await
或Promise.all()
的情况下,代码执行顺序可能不如预期,且没有明确的错误处理。
解决方案:并行化异步请求
如果多个请求之间没有依赖关系,我们就可以通过并行执行请求来减少总的等待时间。我们可以使用 JavaScript 中的 Promise.all()
方法来实现并行化请求。Promise.all()
方法接受一个数组,数组中的每一项都是一个返回 Promise
的异步操作。Promise.all()
会并行执行这些操作,直到所有操作都完成。
优化后的代码如下:
async handleAuthToolInfoPeople() {// 并行执行多个请求,等待所有请求完成const fetchPromises = [this.fetchTreePeople(),this.fetchDeptList(),this.fetchProjectList(),this.fetchBaseList(),this.fetchAuthData(),];try {// 等待所有请求都完成await Promise.all(fetchPromises);} catch (error) {console.error('Error fetching data:', error);}
}
async handleAuthToolInfoPeople() {this.fetchTreePeople()this.fetchDeptList()this.fetchProjectList()this.fetchBaseList()this.fetchAuthData()
}
关键点:
- 并行请求:通过
Promise.all()
将多个异步请求并行执行。每个请求都会同时启动,而不是一个接一个地等待。 await Promise.all(fetchPromises)
:Promise.all()
等待所有的请求都完成。当所有的请求都完成时,控制权才会返回到handleAuthToolInfoPeople()
函数继续执行。- 错误处理:如果有任何一个请求失败,
Promise.all()
会立即抛出错误,并跳转到catch
块。其他请求(即使它们成功完成)也会被中断。
优势:
- 提高效率:所有请求是并行执行的,因此请求的总时长大大缩短,通常是取决于最长请求的时间,而不是所有请求的累加时间。
- 适用于无依赖的请求:当多个请求之间没有依赖关系时,使用并行化可以最大化地提升性能。
为什么并行化可以提升性能?
在并行化请求之后,所有异步操作是同时发起的,减少了总的等待时间。传统的串行请求方式是一个请求完成后才会执行下一个请求,这意味着请求的总时间是所有请求时间的累加。而并行化请求后,总的请求时间就取决于耗时最长的单个请求,这样可以大幅度提升性能。
处理依赖关系:分组执行
在某些情况下,异步请求之间是有依赖关系的,比如 fetchAuthData
需要依赖其他数据才能执行。
在这种情况下,我们不能完全并行化所有请求。但是,我们仍然可以将没有依赖关系的请求进行并行化,等待这些请求完成后再执行依赖请求。这样可以进一步优化性能。
以下是基于依赖关系分组执行请求的优化示例:
async handleAuthToolInfoPeople() {// 并行执行与 fetchAuthData 无关的请求const fetchDataPromises = [this.fetchTreePeople(),this.fetchDeptList(),this.fetchProjectList(),this.fetchBaseList(),];try {// 等待并行的请求完成await Promise.all(fetchDataPromises);// 执行依赖于其他数据的请求await this.fetchAuthData();} catch (error) {console.error('Error fetching data:', error);}
}
通过这种方式,我们在不影响业务逻辑的情况下,尽可能并行化请求,减少了用户等待的时间。
其他优化思路
- 缓存已请求的数据
如果某些请求的数据是静态的或者不常变化的,我们可以考虑在前端缓存这些数据,避免每次都重新请求。比如,可以将数据存储在全局状态管理库(如 Vuex 或 Redux)中,或者本地存储(localStorage)中。 - 合并请求
如果后端接口支持,可以考虑将多个请求合并成一个请求。这不仅可以减少请求的数量,还能减少网络传输的开销,进一步提升性能。 - 优化后端接口
如果前端的优化措施不足以大幅提升性能,可能还需要考虑后端接口的优化。比如,后端能否通过批量查询的方式合并多个请求,返回合并后的数据?后端的响应速度也直接影响前端的性能。
总结
在前端开发中,当面对多个异步请求时,如何有效地管理这些请求以减少等待时间,显得尤为重要。通过并行化请求、分组执行和缓存已请求的数据,我们可以显著提升应用的响应速度和用户体验。