ChatGPT解决这个技术问题 Extra ChatGPT

How to reject in async/await syntax?

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?

Avoid the Promise constructor antipattern! Even the first snippet should have been written foo(id: string): Promise<A> { return someAsyncPromise().then(()=>{ return 200; }, ()=>{ throw 400; }); }
I think it’d be helpful to translate the code in this question into vanilla JS, since the question has nothing to do with TypeScript. If I did so would that edit likely be accepted?
I think the types help make it more understandable - you know exactly what it is returning in each instance.

T
T.J. Crowder

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`.
}

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.
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
is there any reason you would want to throw a new error as opposed to the error given to you in the catch block?
@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.
@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...
D
David

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.


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!
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.
@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.
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.
@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.
A
Andrew

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
}

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.
M
Mak

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


The question specifically asked about using async/await. Not using promises
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.
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
I've edited your response to have it updated. Let me know if I missed something
u
unional

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`
}

P
Pedro Lourenço

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.


I use this construction almost exclusively (much cleaner) and there is a 'to' module that's been around for awhile 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.
R
RiqueW

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.


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!
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.