批量任务的JS异步问题
2024-10-15 16:05:11

引入

项目中遇到了这样一个情景,需要先向后端查询一个表的所有数据,然后这个表的每行数据都向后端查询这行数据的id是否在另一个表中存在。

实际上为了减少网络传输的开支,这个动作应该在后端完成,但是正好借此机会学习这种情况的解决方式。

问题

起初我在第一个调用后的then范围内用for循环调用了若干个Promise请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
refreshList() {
      this.loading = true
      this.processService.list({
        currentPage: this.tablePage.currentPage,
        pageSize: this.tablePage.pageSize
      }).then(({ data }) => {
      this.records = data.records
      for (let i = 0; i < this.records.length; i++) {
    this.processService.isFinish(this.records[i].id).then(({ data }) => {
            this.records[i].done = data.done
          })
        }
        this.tablePage.total = this.records.length
        this.loading = false
      })
    },

但是这样做之后,前端显示的数据会先短暂闪烁,最终不显示。起初我以为是前端控件的问题,排除了很久都找不出问题的原因所在。后来才想到可能是异步程序的问题。

异步问题

首先,即使没有调用多个Promise,这个程序也存在问题。首先.then不会默认等待,在最外层的list调用链中,this.records[i].done的操作是需要异步请求的,这就导致了在这个操作完成前,this.records的值就已经读取完成并且this.loading = false,因此表格已经认为加载完毕,之后等待值传来时就没有完成响应式更新。

问题修复

之后我堆代码进行了如下更改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    async refreshList() {
      this.loading = true
const { data } = await this.processService.list({
currentPage: this.tablePage.currentPage,
pageSize: this.tablePage.pageSize
})
    this.records = data.records
const promises = this.records.map(item => {
return this.processService.isFinish(item.id).then(({ data }) => {
item.done = data;
return item;
})
})
this.records = await Promise.all(promises)
this.tablePage.total = this.records.length
this.loading = false
})
    },

首先第一点就是我增加了async/await。通过await等待可以防止异步程序出现乱序的错误。

然后就是Array.prototype.map()可以返回一个数组,这个数组的每个元素都是原数组每个元素调用一次提供的函数的返回值后形成的。使用这个语法可以简化开发。

再者,学习到了一个新的知识点,Promise.all()支持对多个promise进行集中处理,聚合多个promise。这样也可以对多个promise进行await了。

这次的错误确实加深了对异步编程的印象。