头条笔试题:任务调度器
in JavaScript with 0 comment

头条笔试题:任务调度器

in JavaScript with 0 comment

题目

实现一个带并发限制的异步任务调度器,保证同时最多运行 2 个任务

题目代码:

class Scheduler {
    async add(promiseCreator) {
        // 补全代码
    }
    // 补全代码
}

const timeout = (time) =>
    new Promise((resolve) => {
        setTimeout(resolve, time);
    });

const scheduler = new Scheduler({ limit: 2 });

const addTask = (time, order) => {
    scheduler
        .add(() => timeout(time))
        .then(() => console.log(`order: ${order}`));
};

addTask(400, 4);
addTask(200, 2);
addTask(300, 3);
addTask(100, 1);

// 应该输出:2 4 3 1

思路

题目中的 add 方法前面有个 async 面试官的意思应该是想让面试者通过async/await实现,但我当时并没有任何思路。我的实现思路是:

  1. 新建 Promise 并将 resolvereject 保存起来
  2. 执行中任务未超过限制时,直接 run 执行任务
  3. 超过限制,缓存起来
  4. run 执行任务完成后删除执行队列,并检查缓存队列执行任务

代码实现

class Scheduler {
    constructor(opt = {}) {
        const defaultOption = { limit: 2 };
        const finalOption = Object.assign({}, defaultOption, opt);
        this.queue = [];
        this.holdOn = [];
        this.limit = finalOption.limit;
        this.id = 0;
    }
    add(promiseCreator) {
        return new Promise((resolve, reject) => {
            promiseCreator.resolve = resolve;
            promiseCreator.reject = reject;
            const len = this.queue.length;
            if (len < this.limit) {
                this.run(promiseCreator);
            } else {
                this.holdOn.push(promiseCreator);
            }
        });
    }
    run(promiseCreator) {
        const id = this.id++;
        let promise;
        try {
            promise = promiseCreator().then(() => {
                // 删除已完成
                this.queue = this.queue.filter((i) => i.id !== id);
                // 执行等待队列
                this.checkHoldOn();
                promiseCreator.resolve();
            });
        } catch (error) {
            promiseCreator.reject(error);
        }

        promise.id = id;
        this.queue.push(promise);
    }
    checkHoldOn() {
        if (this.holdOn.length > 0) {
            const task = this.holdOn.shift();
            this.run(task);
        }
    }
}

并没有用到 async/await 网上搜了一下,看到别人的实现,很惊艳,对我的启发很大,原来 async/await 还可以这样用:

class Scheduler2 {
    list = []; //用来承载还未执行的异步

    count = 0; //用来计数

    constructor(limit) {
        this.limit = limit; //允许同时运行的异步函数的最大个数
    }

    async add(fn) {
        if (this.count >= this.limit) {
            // 超出限制的任务会一直 await,直到进行中的任务resolve
            await new Promise((resolve) => {
                this.list.push(resolve);
            });
        }

        this.count++;

        const result = await fn();

        this.count--;
        // 进行中的任务执行完成,resolve() 的是在此任务之后添加的待执行任务,先进先出
        if (this.list.length > 0) this.list.shift()();

        return result;
    }
}

分析: 1,2,3 会同时输出,过 2s 后又会同时输出 4,5,6.最需要引起注意的便是类 Scheduler 中的 add 方法中的第一句,通过 await 阻塞 Promise 但是又不执行 resolve,而是将 resolve 保存到数组当中去,这样就达到了当异步任务超过 limit 时线程就会阻塞在第一行.每执行完一个异步任务就会去数组中查看一下有没有还处于阻塞当中的异步任务,如果有的话就执行最前面的那个异步任务.

参考:js 实现异步任务调度器