前言
我们之前探讨过,对于包含处理状态的表格数据,我们可以通过轮询的方式进行处理(轮询更新进度条:JavaScript中的定时器和异步编程技巧)。然而,当我们离开页面时,定时器仍会继续触发请求,这会造成资源的浪费,因为返回的数据并没有被渲染出来。
为了解决这个问题,我们需要实现以下要求:
- 最多只能同时存在一个轮询的定时器。
- 当用户离开页面时,定时器应被关闭。
- 当用户重新进入页面时,如果仍有数据正在处理中,应重新启动轮询。
- 轮询应能跨页面、跨显示数量。
实现
为了实现上述要求,我们可以采取以下步骤:
- 在全局范围内定义一个唯一的定时器变量,例如
running_task_timer
。 - 在开始轮询之前,检查
running_task_timer
是否已存在。如果存在,则清除该定时器并停止轮询。 - 如果需要启动轮询,首先检查页面是否处于激活状态。如果不在激活状态,则不启动轮询。
- 启动轮询时,设置
running_task_timer
为定时器的ID,并开始轮询处理。 - 当用户离开页面时,清除
running_task_timer
并停止轮询。 - 当用户重新进入页面时,检查是否有正在处理的数据。如果有,则重新设置
running_task_timer
并启动轮询。 - 在轮询处理中,检查页面是否处于激活状态。如果不在激活状态,则暂停轮询处理。
- 在轮询处理完成时,检查是否有新的数据需要处理。如果有,则重新设置
running_task_timer
并启动轮询。 - 通过上述步骤,我们可以确保同时只有一个轮询的定时器存在,并在用户离开页面时关闭定时器。当用户重新进入页面时,如果仍有数据正在处理中,可以重新启动轮询。同时,轮询的处理可以跨页面、跨显示数量进行。
<template>
<div>
<el-table :data="tableData">
<el-table-column prop="id" label="任务ID" />
<el-table-column prop="name" label="任务名称" />
<el-table-column prop="status" label="任务状态" />
<el-table-column label="操作">
<template slot-scope="scope">
<el-button @click="reloadTask(scope.row)">重新执行</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [],
running_task_timer: null,
running_task_list: [],
};
},
created() {
this.getData();
},
beforeDestroy() {
this.clearMyInterval();
},
methods: {
getData() {
axios
.get(url, query)
.then(res => {
if (res.status === 200) {
tableData = res.data.data;
this.addRunningTask();
}
})
.catch(error => alert(error));
},
addRunningTask() {
this.running_task_list = [];
this.tableData.forEach(item => {
if (item.status === 'RUNNING') this.running_task_list.push(item);
});
this.running_task_list.length ? this.startInterval() : this.clearMyInterval();
},
startInterval() {
const _this = this;
this.clearMyInterval();
this.running_task_timer = setInterval(() => _this.getData, 1000);
},
clearMyInterval() {
if (this.running_task_timer !== null) clearInterval(this.running_task_timer);
},
reloadTask(row) {
row.status = 'BEGIN';
axios
.put(url, { id: row.id })
.then(res => {
if (res.status === 200) this.getData();
})
.catch(err => {
alert(err);
row.status = 'DONE';
});
},
},
};
</script>
数据定义
tableData
: 这是一个数组,用于存储从服务器获取的表格数据。running_task_timer
: 这是一个定时器变量,用于轮询正在运行的任务。running_task_list
: 这是一个数组,用于存储状态为“RUNNING”的任务。
生命周期钩子
created()
: 当组件被创建时,它调用getData()
方法来获取数据。beforeDestroy()
: 当组件即将被销毁时,它调用clearMyInterval()
方法来清除定时器,以避免资源浪费。
方法
getData()
: 使用 axios 发送 GET 请求来获取数据。如果响应的状态码是 200,它将更新tableData
并调用addRunningTask()
方法。addRunningTask()
: 遍历tableData
,找出状态为“RUNNING”的任务,并将它们存储在running_task_list
中。如果running_task_list
有内容,则调用startInterval()
方法来启动定时器;否则,调用clearMyInterval()
方法清除定时器。startInterval()
: 首先调用clearMyInterval()
来清除可能已经存在的定时器,然后设置一个新的定时器,每隔1秒调用getData()
方法来轮询数据。clearMyInterval()
: 如果running_task_timer
不为 null,则清除该定时器。reloadTask(row)
: 更新特定行的状态为“BEGIN”,并使用 axios 发送 PUT 请求来重新加载任务。如果响应的状态码是 200,则再次调用getData()
方法。如果请求失败,它会将行的状态设置为“DONE”。
改进
提升性能:只更新修改项数据
<script>
export default {
data() {
return {
tableData: [],
running_task_timer: null,
running_task_list: [],
};
},
created() {
this.getData();
},
beforeDestroy() {
this.clearMyInterval();
},
methods: {
getData() {
axios
.get(url, query)
.then(res => {
if (res.status === 200) {
tableData = res.data.data;
this.addRunningTask();
}
})
.catch(error => alert(error));
},
addRunningTask() {
this.running_task_list = [];
this.tableData.forEach(item => {
if (item.status === 'RUNNING') this.running_task_list.push(item);
});
if (this.running_task_list.length > 0) this.startInterval();
},
startInterval() {
const _this = this;
this.clearMyInterval();
this.running_task_timer = setInterval(() => _this.updateTask, 1000);
},
clearMyInterval() {
if (this.running_task_timer !== null) clearInterval(this.running_task_timer);
},
updateTask() {
if (this.running_task_list.length === 0) this.clearMyInterval();
const queryIds = [];
this.running_task_list.forEach(item => queryIds.push(item.id));
axios.get(url, { params: { ids: queryIds } }).then(res => {
if (res.status === 200) {
res.data.data.forEach(update_item => {
this.running_task_list.forEach(item => {
if (update_item.id === item.id) item = { ...update_item };
// 去除已经完成的任务
if (item.status !== 'RUNNING') this.running_task_list.splice(this.running_task_list.indexOf(item), 1);
});
});
}
});
},
reloadTask(row) {
row.status = 'BEGIN';
axios
.put(url, { id: row.id })
.then(res => {
if (res.status === 200) this.getData();
})
.catch(err => {
alert(err);
row.status = 'DONE';
});
},
},
};
</script>
更新机制的改进:之前的代码可能存在一个性能问题,即定时器会不断轮询服务器,无论运行的条数多少,都会全量更新接口返回的数据,这可能导致不必要的性能浪费。在新的代码中,我们通过在 updateTask
方法中只更新 running_task_list
中存储的需要更新的项,来避免了这一问题。任务完成后,从 running_task_list
中移除,从而在当前页更新完毕后停止轮询。