The code below has a bug I can't understand. I have 2 implementations of consumer. The first one (consumer) works fine when the callback Promise is rejected, and the error is caught by the outer main catch. On the other hand consumer2 throw an unhandled exception UNLESS the following catch is added in the for each loop
p.catch(err=>'this catch is needed with consumer2 to avoid unhandled error propagating to main thread')
What's the difference between consumer and consumer2 here? I thought I could remove the Promise wrapper because async automatically returns a Promise and rejects it when an error is thrown. Plus I thought that the promise in the promises array fetched by consumer gets handled in try catch statement.
async function mapAsync(iterable, concurrency, cb) {
let promisees = []
let consumers = []
let results = Array(iterable.length)
if (concurrency > iterable.length)
concurrency = iterable.length
async function consumer() {
return new Promise(async (resolve, reject) => {
while(promisees.length>0){
try {
const p = promisees.shift()
await p
} catch(err) {
reject(err)
}
}
resolve()
})
}
async function consumer2(){
while(promisees.length>0){
try{
const p = promisees.shift()
await p
} catch(err) {
throw err
}
}
}
iterable.forEach((item, index) => {
const p = Promise.resolve(cb(item))
.then(res=>results[index] = res)
promisees.push(p)
console.log(p)
//p.catch(err=>'this catch is needed with consumer2 to avoid unhandled error propagating to main thread')
})
for(let i=0; i<concurrency; i++) {
consumers.push(consumer2())
}
return Promise.all(consumers).then(()=>results)
}
const test = [0, 42]
mapAsync(test, 5, async (num) => {
throw Error('custom error')
}).then(results => console.log(...results))
.catch(err => console.log('outer main catch: ', err.message))
consumer is a special case of promise constructor antipattern. That promise constructor is async results in a pending promise if error handling isn't precisely correct.
The point of p.catch(() => {}) is to prevent handling of a promise rejection as unhandled, which occurs on the next tick. This requires chaining a promise with catch right after it is created. In consumer2, catch occurs inside a loop that contains await, an error is caught synchronously only from the first promise in the list, the rest can cause unhandled rejections.
Even if an error is handled later, an unhandled rejection event at best leads to redundant error output and at worst to an exception.