从0实现Promise并发控制

182 阅读3分钟

面试中经常会出现,要求实现控制并发队列的题目,入参为异步任务、并发数量,掘金上有很多实现,但是过于优雅;今天从0到1写出 promise 并发队列

该示例使用 class 实现,这里先把使用代码po出来,下面会聚焦于 Queue 的实现

function delay(text,time){
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            resolve(text)
        },time)
    })
}

const p1 = () => delay('1',5000)
const p2 = () => delay('2',2000)
const p3 = () => delay('3',3000)
const p4 = () => delay('4',2000)
const p5 = () => delay('5',3000)


const p = [p1,p2,p3,p4,p5]

class Queue {
    ...
}

new Queue(p, 3)

Queue 功能分析

目前我们仅考虑在初始化时,设置异步任务数组和并发数量(limit),所以在初始化时,可以直接推入limit个异步任务到队列中,随即执行异步任务;

然后在运行异步过程中,每当一个异步任务结束,就推一个新的异步任务进入队列

理论可行,进行实践

初始化

constructor中,除了赋值以外,还需要从异步任务的头部开始,推入limit个任务进入队列中,一旦推入完成,这里我们默认直接进行异步任务的执行;

class Queue {
    constructor(initArr, limit) {
        this.queue = [];
        this.limit = limit;
        this.lists = initArr;

        for (let i = 0; i < this.limit; i++) {
            this.queue.push(this.lists.shift())
        }
        this.work();
    }
    
    work() {
        ...
    }

    limit = 0;
    queue = [];
    lists = [];
}

work的定义

work的功能是执行当前队列(queue)的所有任务,并在其中异步任务完成时,将异步任务数组(lists)中的第一个拿出来传入队列(queue)中;

pop() {
    if (this.lists.length > 0) {
        this.queue.push(this.lists.shift());
    }
}

work() {
    while(this.queue.length > 0) {
        const requestFunc = this.queue.shift();
        const request = requestFunc();
        Promise.resolve(request).finally(res => {
            this.pop();
            this.work();
        })
    }
}

这里使用Promise.resolve 保证request不是promise,代码也可以正常执行。

完整代码

class Queue {
    constructor(initArr, limit) {
        this.queue = [];
        this.limit = limit;
        this.lists = initArr;

        for (let i = 0; i < this.limit; i++) {
            this.queue.push(this.lists.shift())
        }
        this.work();
    }

    pop() {
        if (this.lists.length > 0) {
            this.queue.push(this.lists.shift());
        }
    }

    work() {
        while(this.queue.length > 0) {
            const requestFunc = this.queue.shift();
            const request = requestFunc();
            Promise.resolve(request).finally(res => {
                this.pop();
                this.work();
            })
        }
    }

    limit = 0;
    queue = [];
    lists = [];
}

进阶版

进阶版增加一个能够在使用时,增加任务的功能,例如

const q = new Queue(p, 3)

q.push(() => delay('6', 3000))

所以相比上一个版本,新增一个外部调用的 push 方法;可以向异步队列中推入数组,存在两种情况,一种是可以直接推入队列(queue)中,一种是进入数组中等待调用;所以在push方法中只推入到数组中,同时新增一个pushQueue来处理是否推入队列(queue);

push(item) {
    if (item instanceof Array) {
        this.lists.push(...item);
    } else {
        this.lists.push(item)
    }

    this.pushQueue()
}

pushQueue的方法定义为,检查当前是否能够插入到队列中,如果可以,则插入,否则不执行操作;

pushQueue() {
    if (this.lists.length > 0) {
        for (let i = 0; i < this.limit - this.running; i++) {
            const item = this.lists.shift()
            if (item) this.queue.push(item);
        }
    }
}

这里新增了一个running变量,用来检查当前正在执行的异步任务数量,所以相应的在work方法中,需要在异步任务开始前和开始后,做running变量的增/减

 work() {
    while(this.queue.length > 0) {
        const requestFunc = this.queue.shift();
        const request = requestFunc();
        // 此处增加
        this.running += 1;
        Promise.resolve(request).finally(res => {
            // 此处减少
            this.running -= 1;
            // 这里也不是做简单的pop,而是通过pushQueue进行队列的处理
            this.pushQueue();
            this.work();
        })
    }
}

完整代码为

class Queue {
    constructor(initArr, limit) {
        this.queue = [];
        this.limit = limit;
        this.lists = initArr;

        this.pushQueue();

        this.work();
    }

    pushQueue() {
        if (this.lists.length > 0) {
            for (let i = 0; i < this.limit - this.running; i++) {
                const item = this.lists.shift()
                if (item)
                    this.queue.push(item);
            }
        }
    }

    push(item) {
        if (item instanceof Array) {
            this.lists.push(...item);
        } else {
            this.lists.push(item)
        }

        this.pushQueue()
    }

    work() {
s        while(this.queue.length > 0) {
            const requestFunc = this.queue.shift();
            const request = requestFunc();
            this.running += 1;
            Promise.resolve(request).finally(res => {
                this.running -= 1;
                this.pushQueue();
                this.work();
            })
        }
    }

    limit = 0;
    queue = [];
    lists = [];
    running = 0
}