据说是头条的面试题:
为建立异步调度的思想,可以先降低一下难度,把addTask函数变化一下:
class Scheduler {
add(promiseCreator) {}
// ......
}
function timeout(time) {
return new Promise(resolve => {
setTimeout(resolve, time);
});
}
const scheduler = new Scheduler();
const addTask = (time, order) => {
// scheduler.add(() => timeout(time)).then(() => console.log(order));
// then放进里面
scheduler.add(() => timeout(time).then(() => console.log(order)));
};
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
若了解过Promise的原理,不难联想到,需要建立一个任务队列,发现新任务时根据情况判断直接执行任务还是将其push进任务队列,如下:
// 递归法
class Scheduler {
static max = 2;
constructor() {
this.count = 0;
this.tasks = []; // 存放待运行的任务
}
add(promiseCreator) {
if (this.count < Scheduler.max) {
this.start(promiseCreator);
} else {
this.tasks.push(promiseCreator);
}
}
async start(promiseCreator) {
this.count += 1;
await promiseCreator();
this.count -= 1;
if (this.tasks.length) {
this.start(this.tasks.shift());
// 为什么不直接this.tasks.shift()()?
// 一方面是为使用count,另一方面因为队列中的任务应尽早执行
// 即addTask(400, '4')应排在addTask(300, '3')后,而不是addTask(1000, '1')后
// 正常整个过程应持续1200ms,若直接this.tasks.shift()()则需要1400ms
}
}
}
// ......
const addTask = (time, order) => {
// then在里面
scheduler.add(() => timeout(time).then(() => console.log(order)));
};
//......
上面的方法使用了递归,若不使用那么“明显”的递归,得建立起“转移resolve”的思想:
class Scheduler {
static max = 2;
constructor() {
this.count = 0;
this.tasks = []; // 存放待运行的任务
}
add(promiseCreator) {
if (this.count < Scheduler.max) {
this.start(promiseCreator);
} else {
new Promise(resolve => {
this.tasks.push(resolve);
// resolve() 将常规情况下在这里执行的resolve转移至tasks中
// 什么时候执行resolve,什么时候执行下面then中注册的回调
}).then(() => {
this.start(promiseCreator);
});
}
}
async start(promiseCreator) {
this.count += 1;
await promiseCreator();
this.count -= 1;
if (this.tasks.length) {
this.tasks.shift()(); // 这时就可以直接this.tasks.shift()()了
}
}
}
// ......
将add/start函数改造为async/await模式:
class Scheduler {
static max = 2;
constructor() {
this.count = 0;
this.tasks = []; // 存放待运行的任务
}
async add(promiseCreator) {
if (this.count >= Scheduler.max) {
await new Promise(resolve => {
this.tasks.push(resolve);
});
}
await this.start(promiseCreator); // 注意这里要加await
}
async start(promiseCreator) {
this.count += 1;
await promiseCreator();
this.count -= 1;
if (this.tasks.length) {
this.tasks.shift()();
}
}
}
// ......
由于async函数本就返回一个Promise对象,所以将addTask函数还原为原题,依然正确:
class Scheduler {
static max = 2;
constructor() {
this.count = 0;
this.tasks = []; // 存放待运行的任务
}
async add(promiseCreator) {
if (this.count >= Scheduler.max) {
await new Promise(resolve => {
this.tasks.push(resolve);
});
}
await this.start(promiseCreator);
}
async start(promiseCreator) {
this.count += 1;
await promiseCreator();
this.count -= 1;
if (this.tasks.length) {
this.tasks.shift()();
}
}
// 若改回不使用async/await的模式,如下,同样有“转移resolve”
// add(promiseCreator) {
// return new Promise(outResolve => {
// if (this.count < Scheduler.max) {
// this.start(promiseCreator, outResolve);
// } else {
// new Promise(resolve => {
// this.tasks.push(resolve);
// }).then(() => {
// this.start(promiseCreator, outResolve);
// });
// }
// });
// }
// async start(promiseCreator, resolve) {
// this.count += 1;
// await promiseCreator();
// resolve();
// this.count -= 1;
// if (this.tasks.length) {
// this.tasks.shift()();
// }
// }
}
// ......
const addTask = (time, order) => {
// 原题
scheduler.add(() => timeout(time)).then(() => console.log(order));
};
//......
当然,原题也可以用递归法做(似乎也更好理解一些):
class Scheduler {
static max = 2;
constructor() {
this.count = 0;
this.tasks = []; // 存放待运行的任务
}
add(promiseCreator) {
return new Promise(resolve => {
// 这里可能有点不容易想到,给函数promiseCreator添加一个自定义属性,存储resolve
promiseCreator.end = resolve;
if (this.count < Scheduler.max) {
this.start(promiseCreator);
} else {
this.tasks.push(promiseCreator);
}
});
}
async start(promiseCreator) {
this.count += 1;
await promiseCreator();
promiseCreator.end();
this.count -= 1;
if (this.tasks.length) {
this.start(this.tasks.shift());
}
}
}
// ......
const addTask = (time, order) => {
// 两种都能正确运行
scheduler.add(() => timeout(time)).then(() => console.log(order));
// scheduler.add(() => timeout(time).then(() => console.log(order)));
};
// ......
共勉。