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?
Promise
constructor antipattern! Even the first snippet should have been written foo(id: string): Promise<A> { return someAsyncPromise().then(()=>{ return 200; }, ()=>{ throw 400; }); }
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`.
}
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.
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!
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.
await
failure separately, but I also needed to work with a Promise-based framework which rejected the promises on error.
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.
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
}
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
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`
}
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.
let [err]=
if only checking for errors.
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.
Success story sharing
throw
is better thanPromise.reject()
IMO. Whether tothrow 400
is a different question. In the OP it is rejecting 400, and we can argue that it should reject anError
instead.async
functions, there is noresolve
orreject
function. There'sreturn
andthrow
, which are the idiomatic ways to resolve and reject theasync
function's promise.new
makes that explicit. Also note that you can't leave it out if you have anError
subclass (class MyError extends Error
), so...