[编程题]前端实现异步池

40 阅读1分钟

这本来是一道面试题,非常有趣,我觉得可以整理一下,算是我在Promise知识中的易错题(毕竟我脑子一紧张就容易宕机= =)

class microPoll {
    constructor(num) {
        this.size = num
        this.queue = []
        this.count = 0
    }
    add(cb) {
        this.queue.push(cb)
        this.start()
    }
    start() {
        if (this.count < this.limit && this.queue.length > 0) {
            this.count++
            const cb = this.queue.shift()
            cb.then((v) => {
                return v
            }).catch((e) => {
                throw e
            }).finally(() => {
                this.count--
                this.start()
            })
        }
    }
}
// cb的值// [易错]: 这样看起来是使得cb是一个Promise对象,但问题是Promise中的executor在创建时就已经启动,这将使得异步池无法实现控制异步任务数量的功能
cb = new Promise((resolve) => {
    setTimeout(() => {
        console.log('task [${id}] done at ${new Date()}')
    }, 10000)
})
​
// [正解]
const cb = () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('task [${id}] done at ${new Date()}')
        }, 10000)
    })
}

同时改动microPool

class microPoll {
    constructor(num) {
        this.size = num
        this.queue = []
        this.count = 0
    }
    add(cb) {
        this.queue.push(cb)
        this.start()
    }
    start() {
        if (this.count < this.limit && this.queue.length > 0) {
            this.count++
            const cb = this.queue.shift()
            cb().then((v) => { // 此时异步池可以控制异步任务数量了
                return v
            }).catch((e) => {
                throw e
            }).finally(() => {
                this.count--
                this.start()
            })
        }
    }
}

完整代码及测试:

class microPool {
    constructor(num) {
        this.limit = num
        this.queue = []
        this.count = 0
    }
​
    start() {
        if (this.count < this.limit && this.queue.length > 0) {
            this.count++
            const cb = this.queue.shift()
            cb().then((v) => {
                return v
            }).catch((e) => {
                throw e
            }).finally(() => {
                this.count--
                this.start()
            })
        }
    }
​
    add(cb) {
        this.queue.push(cb)
        this.start()
    }
​
    set(num) {
        this.limit = num
        this.start()
    }
}
​
// -- test --
const pool = new microPool(5)
​
let taskId = 0
const addTest = () => {
    const id = taskId++
    console.log(`task [${id}] start at ${new Date()}`);
    
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log(`task [${id}] done at ${new Date()}`);
            resolve ('ok')
        }, id * 1000)
    })
}
​
for(let i = 0; i < 10; i++) {
    pool.add(addTest)
}

感觉这个功能还有很多可以完善的地方,不过其中的易错点我已经注明。牵涉到异步的问题本身就比同步问题难一些,在紧张的情况下更容易脑子浆糊。复盘时我很快就开始回忆起Promise的细节并发现了代码逻辑的问题。

所以如果下次遇到这种情况还是先深呼吸想想具体的实现、入参出参的类型,可能会比较有帮助。