简体   繁体   中英

Troubles with promise array and async functions

I was trying to implement the Promise.all function using an async function. But I get an error when one of the promises is rejected. This is the implementation I did:

    async function Promise_All_II(promises){
        let results = []
        for(let p of promises)
            results.push(await p)
        return results
    }

    const takeTime = (value, time=1000) => new Promise(resolve => setTimeout(resolve, time, value))

    let promises = [
        Promise.resolve(1),
        takeTime(2),
        Promise.reject('You are fairly dump'),
        takeTime(3)
    ]
    
    console.log('Promise all II:')
    Promise_All_II(promises).then(console.log, console.log)

And this is the error I get when one of the promises is rejected:

Promise all II:
(node:9399) UnhandledPromiseRejectionWarning: You are fairly dump
(node:9399) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:9399) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
You are fairly dump
(node:9399) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

I tried to wrap the for loop with a try-catch but it didn't work. I also tried to place and .catch after p but still didn't work.

I noticed that the same error occurs when you try to do something like this:

let f = async () => {
    let ps = [
        Promise.resolve(10),
        Promise.reject(10),
        Promise.resolve(11),
        Promise.resolve(12)
    ]
    for(let p of ps)
        await p
}

f().catch(v => 
    console.log('something happened')
)

Can someone help me please?

In nodejs, you will get an error about an unhandled rejection if you leave a rejected promise sitting somewhere without a .catch() handler on it when the interpreter gets back to the event loop. That means you can't just leave a rejected promise in a variable while sitting at an await of some other promise. This will trigger an unhandled rejection error. The JS interpreter isn't smart enough to know that you will LATER catch the rejection.

So, when you do this:

results.push(await p)

And, any of the other promises reject, you will get the unhandled rejection because that rejected promise has no handler on it for a rejection at the time it rejects and the await causes the interpreter to go back to the event loop. The interpreter sees you're at idle, a promise has rejected and it has no rejection handler, thus triggering the error.

So, because of the way the unhandled rejections work in nodejs, there really is no way to implement this the way you are trying to do using await and a for loop because this leaves the other promises sitting there with no reject handler during the await .

The usual implementation of Promise.all() involves creating your own promise and using a counter to keep track of when everything has resolved. And, you immediately install both resolve and reject handlers on each promise so they are never in that state that causes the unhandled rejection handler.

Here's a simple implementation of Promise.all() which you can run in this snippet:

 function Promise_All_II(promises) { return new Promise((resolve, reject) => { let cntr = 0; let length = 0; let results; for (let p of promises) { // keep track of index, for later use with result let i = length++; Promise.resolve(p).then(val => { results[i] = val; ++cntr; if (cntr === length) { resolve(results); } }).catch(reject); } if (length === 0) { resolve([]); return; } results = new Array(length); }); } const takeTime = (value, time=1000) => new Promise(resolve => setTimeout(resolve, time, value)) let promises = [ Promise.resolve(1), takeTime(2), Promise.reject('You are fairly dump'), takeTime(3) ]; console.log('Promise all II:') Promise_All_II(promises).then(console.log, console.log);

This immediately installs both a resolve and reject handler on each promise, thus avoiding the unhandled rejection error.

Note, the caller of Promise_All_II() must also have a reject handler.


A few details to note:

  1. This works on any iterable, not just an array as per the Promise.all() spec. It doesn't even have to have a .length property.
  2. This implements "fast reject" (eg reject as soon as any promise rejects) per the Promise.all() spec.
  3. This allows non-promise values to be in the iterable as per the Promise.all() spec. That's why we use Promise.resolve(p).then(...) so any plain value gets wrapped and we can then treat it as a promise.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM