一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
题目要求:实现一个任务队列函数,最多并发执行limit个任务
核心逻辑
- 依次执行 limit 个任务
- 任务完成递归执行下一个任务
- 直到所有任务执行完毕
代码实现
function taskQueue(tasks, limit) {
// 1. 依次执行limit个任务
for (let index = 0; index < limit; index++) {
run(tasks.shift(), tasks);
}
}
function run(task, tasks) {
new Promise((resolve, reject) => {
resolve(execute(task).then());
}).then(() => {
// 2. 任务完成递归执行下一个任务
// 3. 直到所有任务执行完毕
tasks.length && run(tasks.shift(), tasks);
});
}
function execute(url) {
return new Promise(resolve => {
// 模拟异步请求
setTimeout(() => {
console.log("任务:" + url + "完成", new Date());
resolve({ url: url });
}, 1000);
});
}
// 测试
taskQueue([1, 2, 3, 4, 5, 6, 7], 3);
打印信息改为document.write("任务:" + url + "完成", new Date()+'<br />');
我们看一下测试结果:
更进一步
有些面试题还要求按照传入的顺序返回任务结果,那么我们要如何实现呢?
先上代码:
function taskQueue(tasks = [], limit) {
const res = [];
let count = 0; //已经执行的任务数量(已执行不代表已返回)
return new Promise(resolve => {
for (let i = 0; i < limit; i++) {
run();
}
// 任务总数len
const len = tasks.length;
// 执行任务
function run() {
// 这里不能用`count++;`代替哦,大家可以想下为什么?
let current = count++;
//临界条件
if (current >= len) {
res.length === len && resolve(res);
return;
}
console.log("current", current);
// 发送异步请求
execute(tasks[current])
.then(data => {
res.push(data);
// 如果还有任务没执行,就递归执行任务
if (current < len) {
run();
}
})
.catch(err => {
res.push(err);
// 如果还有任务没执行,就递归执行任务
if (current < len) {
run();
}
});
}
});
}
function execute(url) {
return new Promise(resolve => {
// 模拟异步请求
setTimeout(() => {
console.log("任务:" + url + "完成", new Date());
resolve({ url: url });
}, 1000);
});
}
// 测试
(async () => {
const res = await taskQueue([1, 2, 3, 4, 5, 6, 7], 3);
console.log("res", res);
})();
执行结果:
讲解
上面其实包含了并行和串行两个步骤, 并行是指首先同时发起limit个异步操作
for (let i = 0; i < limit; i++) {
run();
}
串行是执行完一个请求之后,紧接着去执行下一个任务:
execute(tasks[current])
.then(data => {
res.push(data);
// 如果还有任务没执行,就递归执行任务
if (current < len) {
run();
}
})
.catch(err => {
res.push(err);
// 如果还有任务没执行,就递归执行任务
if (current < len) {
run();
}
});
还有一个关键点就是临界条件的判断,这里用了两层:
//临界条件
if (current >= len) {
res.length === len && resolve(res);
return;
}
因为任务是异步的,所以current 很可能一定会出现大于len的情况。
试想在current === 5的时候和 current === 6的时候,都会继续执行 run(), 那么肯定会一前一后导致 current 增加了两次。 由于异步请求的返回时间不确定,这个current最后的大小也不一定哦。当然和limit也有关系。
为了说明这一点,我们不妨把current
的打印平移到临界条件判断之前;结果如下:
所以,我们需要在临界条件里面判断,当前返回结果的长度,如果返回了全部的结果就resolve
, 没有的话就再等等,但是不需要继续执行run()
,因为current >= len
说明所有任务都已经被执行过了。
优化
可能大家都看到了上面的代码中有一块重复代码很辣眼睛,特别是对于强迫症三级患者的我就更不能忍了。就是then().catch()
那部分啦。
类比try catch finally
, 是不是也应该有个finally避免成功和失败的冗余逻辑代码呢?
google一下还真有,果然是我少见多怪了。 Promise.prototype.finally()
所以上面的代码精简成了29行!
希望下回再遇到这道题或者类似的题目能够直接套用这种思路,轻松搞定。
function taskQueue(tasks = [], limit) {
const res = [];
let count = 0;
return new Promise(resolve => {
const len = tasks.length;
for (let i = 0; i < limit; i++) {
run();
}
function run() {
let current = count++;
if (current >= len) {
res.length === len && resolve(res);
return;
}
execute(tasks[current])
.then(data => {
res.push(data);
})
.catch(err => {
res.push(err);
})
.finally(() => {
if (current < len) {
run();
}
});
}
});
}
总结
- 限制并发不搜集结果
- 依次执行 limit 个任务
- 任务完成递归执行下一个任务
- 直到所有任务执行完毕
- 限制并发并按传入顺序搜集返回结果
- 依次执行 limit 个任务
- 返回一个promise实例
- 任务完成递归执行下一个任务
- 搜集任务返回结果存入数组
- 直到所有任务执行完毕 并 返回结果数 === 任务数
- promise执行resolve()
熟练掌握 递归, 遍历 等编程技巧,轻松应对面试常考题目。
不常用的知识点很容易忘记,可以收藏起来及时复习下,才能活得更长久的记忆。
更文不易,如果对你有所帮助,欢迎点赞评论,这将成为我继续创作的动力,不胜感激。