nvoigt
nvoigt

Reputation: 77285

How to avoid "uncaught error in promise" with async/await

The following code is an MCVE of my problem:

class MyApp {
    public async crash(): Promise<number> { 
        throw new Error("Hello Error");
    }

    public async chain(promise: Promise<number>) {
        await this.delay(10000);
        return promise;
    }

    private async delay(ms: number): Promise<void> {
       return new Promise<void>( resolve => setTimeout(resolve, ms) );
    }
}

const myApp = new MyApp();

const a = async () => { 
    try {
        const foo = await myApp.chain(myApp.crash());
    } catch (e) {
        console.log("Hello Catch");
    }
};

a();

Link to Typescript Playground

I have a Promise<>, that sooner or later will reject. I have a handler that will await said promise. However, if the rejection is faster then my code leading to the await call, some global error handler will kick in and end my app, telling me I had an uncaught error. Funny side effect: if I wait long enough (10s in my example), it will magically recover and remember that there was an await and a catch block and actually do what's in the catch.

But for those 10 seconds, my script is in the "we are all gonna die!" mode. Some global error handler kicks in, my app is shut down.

What am I doing wrong? Isn't the whole point of being async that I don't have to handle it right now? Why is there even a global error handler?

My real world case is this:

public async showWait<T>(promise: Promise<T>): Promise<T> {
    await loadingSpinner.present();

    try {
        return await promise;
    }
    finally {
        await loadingSpinner.dismiss();
    }
}

If the task to execute crashes faster than I can show a spinner, my whole app will crash. And before you jump in and tell me to add a catch block, please see the MCVE, that's not the problem. I do catch the exception from the awaithigher up the call chain, but the crash happens before the code even reaches the await.

Upvotes: 2

Views: 2918

Answers (1)

Bergi
Bergi

Reputation: 664256

The problem is that you are constructing a promise and not doing anything with it (not installing an error handler!) while waiting for some other promise in the mean time:

let promise = crash();
await loadingSpinner.present();
await promise;

This is a well-known antipattern: Waiting for more than one concurrent await operation

In general your methods should accept values not promises, but in this case it might be warranted if you want to wrap the promise. You still need to immediately handle it though. The proper solution would be

public async showWait<T>(promise: Promise<T>): Promise<T> {
    try {
        const [, val] = await Promise.all([
           loadingSpinner.present(),
           promise,
        ]);
        return val;
    } finally {
        await loadingSpinner.dismiss();
    }
}

Upvotes: 4

Related Questions