Properly handling exceptions from async functions at the top level

I’m having trouble understanding the correct way to handle exceptions from async functions without triggering Node’s UnhandledPromiseRejectionWarning (since that seems to suggest that I’m doing something wrong).

Normally, in non-async code, unhandled exceptions bubble all the way to the top and eventually get printed to the console. The code that resulted in the exception is stopped all the way back up the stack.

Say I have the following code:

test1().catch((e) => { throw e; });
console.log('got here top-level'); // This will print

async function test1() {
    let test2Result = await test2();
    console.log('got here test 1'); // This won't print
    return test2Result;
}

async function test2() {
    throw new Error('something failed here');
}

I would expect that this would do what I want. At the top-most level, it catches the exception from the async functions and re-throws (but now outside any async code or Promises). It should still have the original stack trace so I can find the problem. Happy days, right? But no, this still results in UnhandledPromiseRejectionWarning and I don’t understand why.

What’s the proper way to catch all unhandled exceptions from inside Promises?