面试题:JS实现异步任务调度器

2,060 阅读2分钟

据说是头条的面试题:

image.png

为建立异步调度的思想,可以先降低一下难度,把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)));
};
// ......

共勉。