如何实现Promise并发控制

前端 潘老师 2个月前 (03-03) 20 ℃ (0) 扫码查看

在前端开发的面试环节中,经常会遇到这样一类题目:要求实现控制并发队列,给定异步任务和并发数量作为参数,完成特定的并发控制功能。不少小伙伴面对这类问题时会感到头疼,今天就带大家一步步从0到1实现Promise并发控制,用通俗易懂的方式讲解,保证大家都能看明白。

一、基础功能实现思路

(一)示例代码准备

首先,我们通过一个简单的delay函数来模拟异步任务。这个函数接收两个参数,一个是要返回的文本text,另一个是延迟的时间time。它返回一个Promise,在设定的延迟时间后,resolve返回对应的文本。

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

接着,定义一些具体的异步任务p1p5,它们分别返回不同的文本,并且延迟时间也不一样。

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]

(二)Queue类的初始化

我们要创建一个Queue类来实现并发控制。在初始化这个类的时候,需要设置异步任务数组和并发数量limit。在constructor构造函数里,除了把传入的参数赋值给类的属性外,还要从异步任务数组的开头取出limit个任务,添加到一个任务队列queue里,并且默认直接开始执行这些异步任务。

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不是promise时,代码也能正常执行
        Promise.resolve(request).finally(res => {
            this.pop();
            this.work();
        })
    }
}

这样,基础版本的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 = [];
}

二、进阶功能拓展

有时候,我们希望在使用过程中还能往任务队列里添加新的任务。比如,在已经创建了Queue实例q后,还能通过q.push(() => delay('6', 3000))这样的方式添加新任务。这就需要对之前的代码进行升级。

(一)新增push方法

我们新增一个外部可以调用的push方法。这个方法可以接收一个任务或者一个任务数组。如果传入的是数组,就把数组里的任务都添加到lists数组里;如果传入的是单个任务,就直接把这个任务添加到lists数组里。添加完任务后,调用pushQueue方法来处理是否把新任务添加到任务队列queue里。

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

    this.pushQueue()
}

(二)pushQueue方法的定义

pushQueue方法的作用是检查当前是否有条件把lists数组里的任务添加到任务队列queue中。如果lists数组里有任务,并且当前正在执行的异步任务数量小于并发限制limit,就把lists里的任务添加到queue里。这里新增了一个running变量,用来记录当前正在执行的异步任务数量。

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);
        }
    }
}

(三)修改work函数

因为新增了running变量,所以在work函数里,当开始执行一个异步任务时,要把running加1;当异步任务完成时,要把running减1。同时,任务完成后不再直接调用pop方法,而是调用pushQueue方法来处理任务队列。

work() {
    while(this.queue.length > 0) {
        const requestFunc = this.queue.shift();
        const request = requestFunc();
        // 开始执行异步任务,running加1
        this.running += 1;
        Promise.resolve(request).finally(res => {
            // 异步任务完成,running减1
            this.running -= 1;
            // 通过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() {
        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
}

通过以上一步步的实现,无论是基础的Promise并发控制,还是进阶的动态添加任务功能,都能顺利完成啦。希望这篇文章能帮助大家更好地理解和掌握Promise并发控制的实现原理,在面试和实际开发中都能轻松应对相关问题。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/front/15263.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】