这本来是一道面试题,非常有趣,我觉得可以整理一下,算是我在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的细节并发现了代码逻辑的问题。
所以如果下次遇到这种情况还是先深呼吸想想具体的实现、入参出参的类型,可能会比较有帮助。