0

I know that rejected promises should be handled with error handling.

However, I suspect that this code will not work:

public myFunction(): Promise<number>
{
  try {
    return this.doThingAsync(...); //Returns a promise
  } catch (e) {
    return Promise.resolve(7); //Fallback value
  }
}

Since the first return immediately returns the promise, isn't it true that the try/catch doesn't get a chance to handle the case where the promise gets rejected? I've looked at the transpiled JavaScript and don't see anything special there, not even if I mark the function as async.

Following from that, is await required for handling of rejected promises to work in TypeScript without using the older then/catch syntax from JavaScript?

From my understanding of promises, I would expect the following to work:

public async myFunction(): Promise<number>
{
  try {
    const result = await this.doThingAsync(...);
    return result;
  } catch (e) {
    return 7; //Fallback value
  }
}
Sildoreth
  • 1,883
  • 1
  • 25
  • 38
  • 1
    When you annotate a function with `async` it implicitly returns a Promise. Your function is not async, so you have to *explicitly* return one. You don't need `await`, Just `return Promise.resolve(7);` – Jared Smith Jan 07 '21 at 15:45
  • 1
    Just because something has been around for a while does not mean it should be avoided. You're still using `return` and that's been around forever! TypeScript is a superset of JavaScript; that means there will always be syntax from JavaScript in TypeScript. Indeed, the only thing in the code shown here that is invalid in JavaScript (ES2020) is `public` and `: Promise`. – Heretic Monkey Jan 07 '21 at 15:56
  • I didn't say I was avoiding a certain syntax. There is a correct way to do this with TypeScript syntax, and I want to see it documented here for posterity. I thinks there's a pitfall here that others need to be aware of. – Sildoreth Jan 07 '21 at 16:10
  • 1
    *"I suspect that this code will not work"*: why do you not first test this? Then you'll find that it does or does not work and you could ask something that relates to code you have actually run. – trincot Jan 07 '21 at 16:15
  • TypeScript does not have any special syntax for dealing with promises. You handle promise rejections in exactly the same way as you'd do in JavaScript without type annotations. – Bergi Jan 07 '21 at 21:55
  • @Bergi That's simply not true. Promise rejections in TypeScript result in exceptions and can be handled with try/catch. What I'm doing here is highlighting a potential pitfall of when that is done incorrectly. – Sildoreth Jan 08 '21 at 05:19
  • 1
    @Sildoreth Rejections result in exceptions when you use `await`, yes, but that's not a TypeScript-specific feature. I'm inclined to close as a duplicate of [Difference between `return await promise` and `return promise`](https://stackoverflow.com/q/38708550/1048572) – Bergi Jan 08 '21 at 09:15
  • Does this answer your question? [Difference between \`return await promise\` and \`return promise\`](https://stackoverflow.com/questions/38708550/difference-between-return-await-promise-and-return-promise) – Jared Smith Jan 08 '21 at 13:34
  • 1
    `Promise rejections in TypeScript result in exceptions and can be handled with try/catch` no. No they don't. Typescript is a *static type system*. It compiles away. It doesn't exist at runtime so it can't "handle Promises" any differently than Javascript, in fact it doesn't handle values at runtime *at all*. The only reason that this has anything to do with Typescript is because the compiler is (correctly) warning you that what you *think* you're returning from a function isn't what you're *actually* returning in all cases. – Jared Smith Jan 08 '21 at 13:35

2 Answers2

1

Since the the first return immediately returns the promise, isn't it true that the try/catch doesn't get a chance to handle the case where the promise gets rejected?

Indeed, the asynchronous part that would throw an error would not be running in this function's execution context, and so the try block does not apply to that asynchronous code.

The async/await solution will have that function execution context restored (this is the magic of await), and then the error will be treated within the try...catch context. But there is no such execution context restoration happening in a normal function, so then the try...catch context is not there at the moment of the error.

Here is an example, with a promise that rejects after 100 milliseconds. The first function has the same pattern you started with. It will not see the error, and so there is an "uncaught promise rejection" (Stack Snippets don't show this, but it's in the browser console).

// Helper functions:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const delayAndError = ms => delay(ms).then(() => {throw "my error"});

function myFunction() {
  try {
    return delayAndError(100);
  } catch(e) {
    return Promise.resolve(7); // never happens
  }
}

myFunction().then(console.log); // This promise will reject; so no output

There are essentially two ways to do it right. You already showed the async/await way. But you can do this without that syntax. Instead of a try...catch block, chain a catch method call:

// Helper functions:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const delayAndError = ms => delay(ms).then(() => {throw "my error"});

function myFunction() {
  return delayAndError(100).catch(e => 7);
}

myFunction().then(console.log); // 7
trincot
  • 317,000
  • 35
  • 244
  • 286
0

It is indeed the case that returning a promise within a try/catch will not handle a future rejection of that promise.

Simply switching the method to async won't do the trick, either. For proper error/rejection handling, you need to do one of the following.

Use await

Awaiting the promise ensures that control returns to the method before it terminates, giving it a chance to handle the error. Note that this requires the method/function to be marked async.

public async myFunction(): Promise<number>
{
  try {
    const result = await this.doThingAsync(...);
    return result;
  } catch (e) {
    return 7; //Fallback value
  }
}

Use catch

You could alternately use catch on the promise to specify code that should be executed if/when the promise gets rejected. Note that catch itself returns a promise/thenable.

public myFunction(): Promise<number>
{
    return this.doThingAsync(...).catch(() =>
    {
      return 7; //Fallback value
    }
}
Sildoreth
  • 1,883
  • 1
  • 25
  • 38