464

How can I reject a promise that returned by an async/await function?

e.g. Originally:

foo(id: string): Promise<A> {
  return new Promise((resolve, reject) => {
    someAsyncPromise().then((value)=>resolve(200)).catch((err)=>reject(400))
  });
}

Translate into async/await:

async foo(id: string): Promise<A> {
  try{
    await someAsyncPromise();
    return 200;
  } catch(error) {//here goes if someAsyncPromise() rejected}
    return 400; //this will result in a resolved promise.
  });
}

So, how could I properly reject this promise in this case?

Scheintod
  • 7,953
  • 9
  • 42
  • 61
Phoenix
  • 4,773
  • 2
  • 10
  • 6

8 Answers8

506

Your best bet is to throw an Error wrapping the value, which results in a rejected promise with an Error wrapping the value:

} catch (error) {
    throw new Error(400);
}

You can also just throw the value, but then there's no stack trace information:

} catch (error) {
    throw 400;
}

Alternately, return a rejected promise with an Error wrapping the value, but it's not idiomatic:

} catch (error) {
    return Promise.reject(new Error(400));
}

(Or just return Promise.reject(400);, but again, then there's no context information.)

In your case, as you're using TypeScript and foo's return value is Promise<A>, you'd use this:

return Promise.reject<A>(400 /*or Error*/ );

In an async/await situation, that last is probably a bit of a semantic mis-match, but it does work.

If you throw an Error, that plays well with anything consuming your foo's result with await syntax:

try {
    await foo();
} catch (error) {
    // Here, `error` would be an `Error` (with stack trace, etc.).
    // Whereas if you used `throw 400`, it would just be `400`.
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 14
    And since async/await is about taking the async flow back to sync syntax, `throw` is better than `Promise.reject()` IMO. Whether to `throw 400` is a different question. In the OP it is rejecting 400, and we can argue that it should reject an `Error` instead. – unional Feb 25 '17 at 08:26
  • 2
    Yes, however, if your code chain is really using async/await, then you will.....to hard to type here, let me demo as an answer – unional Feb 25 '17 at 08:32
  • 1
    is there any reason you would want to throw a new error as opposed to the error given to you in the catch block? – Adrian M Apr 30 '18 at 01:12
  • @AdrianM: My impression was the OP wanted to have the error be the status code (based on the code in the question). I'd use the error I received if I didn't have a reason to do something else (or not catch it, and leave error handling to the caller). – T.J. Crowder Apr 30 '18 at 06:42
  • According to [MDN on await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#Description), the `await` expression throws on a call to `reject()`. So wouldn't it be most idiomatic to still call `resolve()` *and* `reject()` in the `Promise` and wrap the `await` call in a `try`/`catch` block? – sebastian Sep 26 '19 at 23:41
  • 1
    @sebastian - I don't know what you mean there. In `async` functions, there is no `resolve` or `reject` function. There's `return` and `throw`, which are the idiomatic ways to resolve and reject the `async` function's promise. – T.J. Crowder Sep 27 '19 at 06:25
  • Can leave out the "new" in "new Error". See https://stackoverflow.com/a/13294683/145400. – Dr. Jan-Philip Gehrcke Nov 28 '19 at 13:04
  • 1
    @Jan-PhilipGehrcke - You *can*, but I never do. It's creating an instance, `new` makes that explicit. Also note that you can't leave it out if you have an `Error` subclass (`class MyError extends Error`), so... – T.J. Crowder Nov 28 '19 at 13:06
202

It should probably also be mentioned that you can simply chain a catch() function after the call of your async operation because under the hood still a promise is returned.

await foo().catch(error => console.log(error));

This way you can avoid the try/catch syntax if you do not like it.

David
  • 7,387
  • 3
  • 22
  • 39
  • 2
    So if I want to reject my `async` function, I throw exception and then catch it nicely with `.catch()` just like if I returned `Promise.reject` or called `reject`. I like it! – icl7126 Apr 27 '18 at 06:49
  • 10
    I don't understand why this should be the accepted answer. Not only is the accepted answer cleaner, but it also handles all possible `await` failures in one routine. Unless very specific cases are needed for each `await` I don't see why you'd want to catch them like this. Just me humble opinion. – mesllo Sep 04 '18 at 18:36
  • 2
    @jablesauce for my use case, not only did I need to catch each `await` failure separately, but I also needed to work with a Promise-based framework which rejected the promises on error. – Reuven Karasik Oct 26 '18 at 17:42
  • It didn't work for me. Doesn't seem to be going in catch block if url fails. [response] = await oauthGet( `${host}/user/permissions/repositories_wrong_url/`, accessToken, accessTokenSecret).catch(err => { logger.error('Unable to fetch repository permissions', err); callback(err); }) – sn.anurag Nov 08 '18 at 08:21
  • 1
    doesn't needed `await` keyword here. – Ashish Rawat Mar 17 '19 at 00:17
  • 4
    I like to use this whenever I can, but if the intended behaviour of the catch is to return from the outer scope (scope where foo() was invoked in), **then you cannot use this solution**. In that case, I am forced to use the try-catch block, because a return statement inside the lambda function of the catch will only return from the lambda function and not from the outer scope. – jose Mar 27 '19 at 17:17
  • @edgaralienfoe This is really case dependent. There are cases when Promise rejection does not always result in an error in your application. To me, sometimes using .catch(..) to work with the error seems cleaner. – Jiri Kralovec Feb 18 '20 at 19:56
  • 3
    @AshishRawat Not true. Without the `await` keyword, a pending promise is returned to the awaiting variable. With the `await` keyword present, it ensures that (if the promise is resolved without error) the return value is the resolved promise result. – shennan Nov 22 '20 at 08:27
17

You can create a wrapper function that takes in a promise and returns an array with data if no error and the error if there was an error.

function safePromise(promise) {
  return promise.then(data => [ data ]).catch(error => [ null, error ]);
}

Use it like this in ES7 and in an async function:

async function checkItem() {
  const [ item, error ] = await safePromise(getItem(id));
  if (error) { return null; } // handle error and return
  return item; // no error so safe to use item
}
Andrew
  • 3,733
  • 1
  • 35
  • 36
  • 4
    Looks like an attempt to have the lovely Go syntax but without much of the elegance. I find the code using it to be obfuscated just enough to suck the value out of the solution. – Kim Jun 21 '18 at 20:02
14

A better way to write the async function would be by returning a pending Promise from the start and then handling both rejections and resolutions within the callback of the promise, rather than just spitting out a rejected promise on error. Example:

async foo(id: string): Promise<A> {
    return new Promise(function(resolve, reject) {
        // execute some code here
        if (success) { // let's say this is a boolean value from line above
            return resolve(success);
        } else {
            return reject(error); // this can be anything, preferably an Error object to catch the stacktrace from this function
        }
    });
}

Then you just chain methods on the returned promise:

async function bar () {
    try {
        var result = await foo("someID")
        // use the result here
    } catch (error) {
        // handle error here
    }
}

bar()

Source - this tutorial:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Mak
  • 894
  • 2
  • 8
  • 24
OzzyTheGiant
  • 711
  • 8
  • 21
  • 7
    The question specifically asked about using async/await. Not using promises – Mak Jan 28 '18 at 22:12
  • This answer was not meant to be the definitive correct answer. This was a support answer to the other answers given above. I would have put it down as a comment but given that I have code, the answer field is a better place. – OzzyTheGiant Jan 30 '18 at 04:07
  • Thanks for clarifying. Showing how to make an async function is definitely helpful. Updating the second code block to use await will be a lot more relevant and useful. Cheers – Mak Feb 11 '18 at 00:25
  • I've edited your response to have it updated. Let me know if I missed something – Mak Feb 11 '18 at 00:43
8

This is not an answer over @T.J. Crowder's one. Just an comment responding to the comment "And actually, if the exception is going to be converted to a rejection, I'm not sure whether I am actually bothered if it's an Error. My reasons for throwing only Error probably don't apply."

if your code is using async/await, then it is still a good practice to reject with an Error instead of 400:

try {
  await foo('a');
}
catch (e) {
  // you would still want `e` to be an `Error` instead of `400`
}
unional
  • 14,651
  • 5
  • 32
  • 56
6

I have a suggestion to properly handle rejects in a novel approach, without having multiple try-catch blocks.

import to from './to';

async foo(id: string): Promise<A> {
    let err, result;
    [err, result] = await to(someAsyncPromise()); // notice the to() here
    if (err) {
        return 400;
    }
    return 200;
}

Where the to.ts function should be imported from:

export default function to(promise: Promise<any>): Promise<any> {
    return promise.then(data => {
        return [null, data];
    }).catch(err => [err]);
}

Credits go to Dima Grossman in the following link.

Pedro Lourenço
  • 605
  • 7
  • 16
  • 1
    I use this construction almost exclusively (much cleaner) and there is a 'to' module that's been around for awhile https://www.npmjs.com/package/await-to-js. Don't need the separate declaration just put let in front of the deconstructed assignment. Also can do just `let [err]=` if only checking for errors. – DKebler Jan 08 '20 at 21:16
3

I know this is an old question, but I just stumbled across the thread and there seems to be a conflation here between errors and rejection that runs afoul (in many cases, at least) of the oft-repeated advice not to use exception handling to deal with anticipated cases. To illustrate: if an async method is trying to authenticate a user and the authentication fails, that's a rejection (one of two anticipated cases) and not an error (e.g., if the authentication API was unavailable.)

To make sure I wasn't just splitting hairs, I ran a performance test of three different approaches to that, using this code:

const iterations = 100000;

function getSwitch() {
  return Math.round(Math.random()) === 1;
}

function doSomething(value) {
  return 'something done to ' + value.toString();
}

let processWithThrow = function () {
  if (getSwitch()) {
    throw new Error('foo');
  }
};

let processWithReturn = function () {
  if (getSwitch()) {
    return new Error('bar');
  } else {
    return {}
  }
};

let processWithCustomObject = function () {
  if (getSwitch()) {
    return {type: 'rejection', message: 'quux'};
  } else {
    return {type: 'usable response', value: 'fnord'};
  }
};

function testTryCatch(limit) {
  for (let i = 0; i < limit; i++) {
    try {
      processWithThrow();
    } catch (e) {
      const dummyValue = doSomething(e);
    }
  }
}

function testReturnError(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithReturn();
    if (returnValue instanceof Error) {
      const dummyValue = doSomething(returnValue);
    }
  }
}

function testCustomObject(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithCustomObject();
    if (returnValue.type === 'rejection') {
      const dummyValue = doSomething(returnValue);
    }
  }
}

let start, end;
start = new Date();
testTryCatch(iterations);
end = new Date();
const interval_1 = end - start;
start = new Date();
testReturnError(iterations);
end = new Date();
const interval_2 = end - start;
start = new Date();
testCustomObject(iterations);
end = new Date();
const interval_3 = end - start;

console.log(`with try/catch: ${interval_1}ms; with returned Error: ${interval_2}ms; with custom object: ${interval_3}ms`);

Some of the stuff that's in there is included because of my uncertainty regarding the Javascript interpreter (I only like to go down one rabbit hole at a time); for instance, I included the doSomething function and assigned its return to dummyValue to ensure that the conditional blocks wouldn't get optimized out.

My results were:

with try/catch: 507ms; with returned Error: 260ms; with custom object: 5ms

I know that there are plenty of cases where it's not worth the trouble to hunt down small optimizations, but in larger-scale systems these things can make a big cumulative difference, and that's a pretty stark comparison.

SO… while I think the accepted answer's approach is sound in cases where you're expecting to have to handle unpredictable errors within an async function, in cases where a rejection simply means "you're going to have to go with Plan B (or C, or D…)" I think my preference would be to reject using a custom response object.

RiqueW
  • 212
  • 1
  • 12
  • 4
    Also, remember that you don't need to get stressed about handling unanticipated errors within an async function if the call to that function is within a try/catch block in the enclosing scope since — unlike Promises — async functions bubble their thrown errors to the enclosing scope, where they're handled just like errors local to that scope. That's one of the main perks of async/await! – RiqueW Oct 31 '18 at 06:36
  • 1
    Microbenchmarks are the devil. Look closer at the numbers. You need to be doing something 1000x to notice a 1ms difference here. Yes, adding throw/catch will deoptimize the function. But a) if you're waiting for something async it's likely to take several orders of magnitude take longer than 0.0005 Ms to happen in the background. b) you need to be doing it 1000x to make a 1ms difference here. – Jamie Pate Apr 15 '20 at 16:53
0

if you don't want to try/catch,and you don't care error msg

const f = await <PromiseHandle>.catch(console.error)

or you can package promise function so that it can always resolve

  function p() {
    return new Promise((resolve) => {
      if (success) {
        resolve({
          data: 'success value',
          code: 200,
          msg: null
        })
      }
      if (error) {
        // # package a layer of the same format as resolve
        resolve({
          data: null,
          code: 400,
          msg: error
        })
      }
    })
  }

that is my personal guesses of feasible solutions,if you have more convenient and feasible methods, please let me know