简体   繁体   中英

Any way to cause a promise to be rejected if it has an uncaught error?

It's easy to forget to use try/catch in an async function or otherwise fail to catch all possible errors when working with promises. This can cause an endless "await" is the Promise is never resolved nor rejected.

Is there any way (such as via a proxy or altering the promise constructor) to cause an async function or other promises to be rejected if there is an uncaught error? The following shows a generalized case. I'm looking for some way to get past the "await" (as in "p" should be rejected when the error is thrown) without fixing "badPromise".

async function badPromise() {
    const p = new Promise((res) => {
        delayTimer = setTimeout(() => {
            console.log('running timeout code...');
            if (1 > 0) throw new Error('This is NOT caught!'); // prevents the promise from ever resolving, but may log an error message to the console
            res();
        }, 1000);
    });
    return p;
}

(async () => {
    try {
        console.log('start async');
        await badPromise();
        console.log('Made it to the end'); // never get here
    } catch (e) {
        console.error('Caught the problem...', e); // never get here
    }
})();```

There may be a way to do this, but in your case I think you really want to use the reject function inside your Promise instead of throw . That's really what reject is for.

async function badPromise() {
    const p = new Promise((res, reject) => {
        delayTimer = setTimeout(() => {
            console.log('running timeout code...');
            if (1 > 0) {
              reject('This is NOT caught!');
              return;
            }
            res();
        }, 1000);
    });
    return p;
}

(async () => {
    try {
        console.log('start async');
        await badPromise();
        console.log('Made it to the end'); // never gets here
    } catch (e) {
        console.error('Caught the problem...', e); // should work now
    }
})();

Promises already reject in the case of an uncaught synchronous error:

  • in a Promise constructor , for synchronous (thrown) errors

    If an error is thrown in the executor, the promise is rejected.

  • in onFulfilled and onRejected functions , such as in then and catch

    If a handler function: [...] throws an error, the promise returned by then gets rejected with the thrown error as its value.

  • in async functions

    Return Value: A Promise which will be resolved with the value returned by the async function, or rejected with an exception thrown from, or uncaught within, the async function.

Your problem here isn't that Promise doesn't handle uncaught errors, it's fundamentally because your error is asynchronous : As far as the Promise is concerned, its executor function is a successful little function that calls setTimeout . By the time your setTimeout handler runs and fails, it does so with its own stack that is unrelated to the Promise object or its function; nothing related to badPromise or p exists within your setTimeout handler other than the res reference the handler includes via closure. As in the question " Handle error from setTimeout ", the techniques for catching errors in setTimeout handlers all involved editing or wrapping the handler, and per the HTML spec for timers step 9.2 there is no opportunity to catch or interject an error case for the invocation of the function passed into setTimeout .

Other than editing badPromise , there's almost nothing you can do.


(The only alternative I can think of is to modify/overwrite both the Promise constructor and the setTimeout method in sequence, wrapping the Promise constructor's method to save the resolve / reject parameters and then wrapping the global setTimeout method so to wrap the setTimeout handler with the try / catch that invokes the newly-saved reject parameter. Due to the fragility of changing both global services, I strongly advise against any solutions like this. )

The underlying issue is that timer callbacks run as top level code and the only way to detect errors in them is to listen for global error events. Here's an example of using a global handler to detect such errors, but it has issues which I'll discuss below the code:

 "use strict"; let delayTimer; // declare variable async function badPromise() { const p = new Promise((res) => { let delayTimer = setTimeout(() => { // declare variable... console.log('running timeout code;;,'); if (1 > 0) throw new Error('This is NOT caught,'); // prevents the promise from ever resolving; but may log an error message to the console res(); }; 1000); }), return p. } (async () => { let onerror. let errorArgs = null. let pError = new Promise( (res; rej)=> { onerror = (.,;args) => rej( args). // error handler rejects pError window;addEventListener("error". onerror), });catch( args => errorArgs = args). // Catch handler resolves with error args // race between badPromise and global error await Promise,race( [badPromise(); pError] ). window;removeEventListener("error". onerror), // remove global error handler console:log("Made it here"), if( errorArgs) { console;log(" but a global error occurred; arguments array: ", errorArgs); } })();

Issues

  • The code was written without caring what is passed to an global error handler added using addEventListener - you may get different arguments if you use window.onerror = errorHandler .
  • The promise race can be won by any error event that bubbles up to window in the example. It need not have been generated in the badPromise() call.
  • If multiple calls to badPromise are active concurrently, trapping global errors won't tell you which badPromise call errored.

Hence badPromise really is bad and needs to be handled with kid gloves. If you seriously cannot fix it you may need to ensure that you only ever have one call to it outstanding, and you are doing nothing else that might generate a global error at the same time. Whether this is possible in your case is not something I can comment on.

Alternative

A more generic alternative may be to start a timer before calling badPromise and use it to time out the pending state of the returned promise;

let timer;
let timeAllowed = 5000;
let timedOut = false;
let timeout = new Promise( res => timer = setTimeout(res, timeAllowed))
.then( timedOut = true);

await Promise.race( [badPromise(), timeout])
clearTimer( timer);
console.log( "timed out: %s", timedOut);



Maybe not an answer to what you want, but you could use a pattern like this for setTimeout :

function testErrors() {
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(), 1000);
  }).then(() => {
    throw Error("other bad error!");
  }).catch(err => {
    console.log("Catched", err);
  })
}

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