前言
之前面试一家公司就遇到这道代码题,当时只是知道应该要用Promise.all() 来处理,但是不清楚应该怎么写,当然那次面试也就挂了,今天刚好看到 前端一个月面试小记,字节、蚂蚁、美团、滴滴 有这道题目。
题目
第一种形式
实现 Scheduler.add() 函数
const timeout = (time) => new Promise(resolve => {
setTimeout(resolve, time)
})
const scheduler = new Scheduler()
const addTask = (time, order) => {
scheduler.add(() => timeout(time))
.then(() => console.log(order))
}
// 限制同一时刻只能执行2个task
addTask(4000, '1')
addTask(3500, '2')
addTask(4000, '3')
addTask(3000, '4')
.....
//Scheduler ?
//4秒后打印1
//3.5秒打印2
//3进入队列,到7.5秒打印3
//...
第二种形式
function asyncPool () {
...
}
const timeout = (i) => {
console.log('开始', i);
return new Promise((resolve) => setTimeout(() => {
resolve(i);
console.log('结束', i);
}, i));
};
asyncPool(2, [5000, 4000, 3000, 2000], timeout).then(res => {
console.log(res);
});
答案
第一种
class Scheduler {
constructor() {
this.awatiArr = [];
this.count = 0;
}
async add(promiseCreator) {
if (this.count >= 2) {
// 函数执行停顿, 3 4 堵塞 为什么是使用 new Promise 进行堵塞呢? 其实就是 把 3 4 的resolve 给1 2通过的人来使用 resolve 表示结束这个等待。
await new Promise((resolve) => {
this.awatiArr.push(resolve);
});
}
this.count++;
const res = await promiseCreator();
this.count--;
if (this.awatiArr.length) {
// 前面promise的resolve, 只要1 2 有一个执行完,就把 3 的停顿终止
this.awatiArr.shift()();
}
return res;
}
}
// 这里做了一个验证
const timeout = (time) => {
console.log('开始', time);
return new Promise((resolve) => {
setTimeout(() => {
resolve();
console.log('结束', time);
}, time);
});
};
const scheduler = new Scheduler();
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then(() => console.log(order));
};
// 限制同一时刻只能执行2个task
addTask(4000, '1');
addTask(3500, '2');
addTask(4000, '3');
addTask(3000, '4');
总结: 请求使用 count 来计算以及正在执行的请求,然后使用新的 promise 来进堵塞后续的请求,把 promise 的 resolve 函数暴露出去,然后执行完的请求来结束还在等待中的请求。
第二种
async function asyncPool(poolLimit, array, iteratorFn) {
const ret = []; // 用于存放所有的promise实例
const executing = []; // 用于存放目前正在执行的promise
for (const item of array) {
const p = Promise.resolve(iteratorFn(item));
ret.push(p);
// 当请求数量大于 限制数
if (poolLimit <= array.length) {
// then回调中,当这个promise状态变为fulfilled后,将其从executing列表中删除
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
// 正在请求的数量已经大于 限制数
if (executing.length >= poolLimit) {
// 一旦正在执行的promise列表数量等于限制数,就使用Promise.race等待某一个promise状态发生变更,
// 状态变更后,就会执行上面then的回调,将该promise从executing中删除,
// 然后再进入到下一次for循环,生成新的promise进行补充
await Promise.race(executing);
}
}
}
return Promise.all(ret);
}
const timeout = (i) => {
console.log('开始', i);
return new Promise((resolve) =>
setTimeout(() => {
resolve(i);
console.log('结束', i);
}, i),
);
};
asyncPool(2, [5000, 4000, 3000, 2000], timeout).then((res) => {
console.log(res);
});
总结: 这里有两个数组 一个是 ret 给 promise.all 使用的,executing 是提供给 Promise.race 执行。使用 Promise.race 堵塞住 for 循环。
Promise.resolve 和 new Promise
Promise.resolve()与new Promise(r => r(v))
总结
两道题差不多都是异曲同工。 都是在前面请求到达请求限制,然后暂停。当前面的请求 Promise 状态变成 fulfilled, 就取消前面的暂停。 第一个暂停是函数执行的停端,第二种是 for循环的停端。