Breaking promise chain with thrown exception - javascript

Currently, if there is an error caught in asyncFunction1()s promise callback, the app will correctly throw the 'Problem A' exception. However, this is passed through the promise chain and the app will eventually see 'Problem B', which means the app is showing the wrong error to the user.
I effectively need to abort execution and break the chain whilst throwing the relevant error. How can I do this?
The HttpsError class information can be found here: https://firebase.google.com/docs/reference/functions/functions.https.HttpsError
It explicitly mentions:
Make sure to throw this exception at the top level of your function
and not from within a callback, as that will not necessarily terminate
the function with this exception.
I seem to have fallen into this trap, but do not know how to work around it. If someone could help me refactor the code so that I can effectively catch and handle these errors properly that would be much appreciated.
exports.charge = functions.https.onCall(data => {
asyncFunction1()
.then(() => {
asyncFunction2();
})
.catch((err) => {
throw new functions.https.HttpsError(
'not-found',
'Problem A'
);
})
.then(() => {
asyncFunction3();
})
.catch((err) => {
throw new functions.https.HttpsError(
'not-found',
'Problem B'
);
})
});

There are a number of different ways to approach this:
You can have each async function just set the appropriate error when it rejects so you don't have to manually add the right error in your own .catch().
You can test in the last .catch() to see if an appropriate error has already been set and just rethrow it if so rather than override it with another error.
You can put the last .catch() on the asyncFunction3() call directly instead of on the whole chain like this so you're targeting only a rejection from that function with that error code:
Modified code:
exports.charge = functions.https.onCall(data => {
return asyncFunction1().then(() => {
return asyncFunction2();
}).catch((err) => {
// set appropriate error for rejection in either of the first two async functions
throw new functions.https.HttpsError('not-found', 'Problem A');
}).then(() => {
return asyncFunction3().catch((err) => {
// set appropriate error for rejection in asyncFunction3
throw new functions.https.HttpsError('not-found', 'Problem B');
});
});
});
Note: I've also added several return statements to make sure promises are being linked into the chain and returns from the exported function. And, I've condensed the logic to make it easier to read.
This might also be a case for async/await (though I'm not entirely sure if functions.https.onCall() allows this or not):
exports.charge = functions.https.onCall(async (data) => {
try {
await asyncFunction1()
await asyncFunction2();
} catch(e) {
throw new functions.https.HttpsError('not-found', 'Problem A');
}
try {
await asyncFunction3();
} catch(e) {
throw new functions.https.HttpsError('not-found', 'Problem B');
}
});

Would something like this work?
exports.charge = functions.https.onCall(data => {
return Promise.resolve().then(
() => {
return asyncFunction1();
}).then(
() => {
return asyncFunction2();
}).then(
() => {
return asyncFunction3();
}).catch(
err => {
throw new functions.https.HttpsError(err);
});
}

Related

How to test that an error is re-thrown in a catch statement with Jest

I have a function that returns a promise, but I want to test that it has a catch defined, and then additionally test that it re-throws the error.
This is a very contrived example but it was the clearest way to show the issue. In my actual code, I am calling a function that is mocked to fail (vs the manually rejecting in this example), and I have additional logging in the catch statement, which explains the re-throwing of the error.
const foo = () => {
return new Promise((resolve, reject) => {
reject(new Error('reject')); // manually rejecting to mimic actual code...
}).catch(error => {
// do some additional logging...
throw error;
});
};
it('should catch and re-throw error', () => {
// Received function did not throw
// and
// Unhandled promise rejection
expect(() => foo()).toThrow();
// Test passes, even when `throw error` is commented out with false positive
expect(foo()).rejects.toThrow();
});
I can successfully check that the logging function is called, but can't figure out how to ensure the error is re-thrown after.
WORKING UPDATE :)
thanks to #skyboyer & #Bergi for getting me to think about the issue a bit differently, and exposing me to some of the finer points of jest
Below is both the updated code to show the logging function, and the updated tests i settled on.
The issues that led to this were
unable to test logging was called due to the error being re-thrown
unable to test the value of the error being re-thrown
Catching the rejected promise allowed me to do both.
I was going to leave in the rejects.toEqual test, but it seems redundant now...
interested in any feedback! and thanks again!
// myModule.js
export const logging = () => {};
export const bar = () => new Promise(resolve => {});
export const foo = () => {
return bar().catch(error => {
logging();
throw error;
});
};
describe('myModule', () => {
let fooReturn;
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(myModule, 'bar').mockImplementation(() => {
return Promise.reject({ error: 'bar error' });
});
jest.spyOn(myModule, 'logging').mockImplementation(() => {});
fooReturn = myModule.foo();
});
it('should catch and re-throw error', () => {
expect.assertions(1);
fooReturn.catch(result => expect(result).toEqual({ error: 'bar error' }));
// removed since the above test covers that the promise was rejected
// return fooReturn.rejects.toEqual(expect.anything());
});
it('should call the loggin method', async () => {
expect.assertions(1);
// prevents UnhandledPromiseRejectionWarning
fooReturn.catch(() => {});
expect(myModule.logging).toBeCalled();
});
});
You missed return.
https://jestjs.io/docs/asynchronous#resolves--rejects
Be sure to return the assertion—if you omit this return statement, your test will complete before the promise returned from fetchData is resolved and then() has a chance to execute the callback.
Your test should be
it('should catch and re-throw error', () => {
return expect(foo()).rejects.toEqual(expect.anything());
});
As u/Bergi noticed with async/await it may look more laconic:
it('should catch and re-throw error', async () => {
await expect(foo()).rejects.toEqual(expect.anything());
});
but if we miss to add await before our expect we will have exact the same issue as in version 1 without return. So beware.

Firebase Functions How To Handle Errors Properly [duplicate]

This question already has an answer here:
Google Cloud Functions - warning Avoid nesting promises promise/no-nesting
(1 answer)
Closed 3 years ago.
NOTE: this question is mainly about error handling, and if this is an ok approach, not about nesting promises, please read before closing
Since there are currently no error codes for services like firestore and firebase database, i'm using a system to know where the function failed and to handle error accordingly, simplified version below:
exports.doStuff = functions.https.onCall((data, context) => {
return [promise doing stuff goes here].catch(error => { throw new Error('ERROR0') })
.then(result => {
return [promise doing stuff goes here, needs result of previous promise]
.catch(error => { throw new Error('ERROR1') })
})
.then(result => {
return [promise doing stuff goes here, needs result of previous promise]
.catch(error => { throw new Error('ERROR2') })
})
.then(result => {
//inform client function successful
return {
success: true
}
})
.catch(error => {
if (error !== null) {
switch (error.message) {
case 'ERROR0':
//do stuff
throw new functions.https.HttpsError('unknown', 'ERROR0');
case 'ERROR1':
//do stuff
throw new functions.https.HttpsError('unknown', 'ERROR1');
case 'ERROR2':
//do stuff
throw new functions.https.HttpsError('unknown', 'ERROR2');
default:
console.error('uncaught error: ', error);
throw error;
}
}
});
});
the thing is, for each .catch() inside each returned promise, i'm getting the following warning: warning Avoid nesting promises
so my question is, is there a better way to handle errors?
Ultimately it's a style recommendation to prevent bizarre and hard to recognise errors. Most of the time a rewrite can eliminate the warning. As an example, you could rewrite your code as the following whilst retaining the same functionality.
exports.doStuff = functions.https.onCall(async (data, context) => {
const result1 = await [promise doing stuff goes here]
.catch(error => {
throw new functions.https.HttpsError('unknown', 'ERROR0', { message: error.message } )
});
const result2 = await [promise based on result1 goes here]
.catch(error => {
throw new functions.https.HttpsError('unknown', 'ERROR1', { message: error.message } )
});
const result3 = await [promise based on result1/result2 goes here]
.catch(error => {
throw new functions.https.HttpsError('unknown', 'ERROR2', { message: error.message } )
});
return {
success: true
};
});
Lastly, rather than using unknown everywhere, you could use one of several possible values for the first argument whilst passing in whatever supporting information you need as the third argument (as shown above where I pass through the original error message).

how to differentiate error's source based on stage in Promise chain

I have 2 callback that both call API and return Promise. They should go sequentially. Let's name them verifyThing and updateThing.
So without error handling it would be as easy as
verifyThing()
.then((id) => updateThing(id))
But now error handling comes. Suppose I need to display different error message once verifyThing fails or updateThing. Also obviously I don't need to call updateThing if verifyThing fails.
So far I've tried with custom Error:
class VerifyError extends Error {};
verifyThing()
.catch(e => {
message("cannot verify");
throw new VerifyError();
})
.then((id) => updateThing(id))
.catch(e => {
if (e instanceof VerifyError) return;
message("cannot update");
})
Unfortunately custom Error check does not work with Babel 6.26 we use. As a dirty patch I can throw magic string instead of Error subclass, but ESLint rules are for a reason, right?
So finally I have got working variant. But I believe it has worse readability(because of nesting):
verifyThing()
.catch(e => {
message("cannot verify");
throw e;
})
.then((id) =>
updateThing(id)
.catch(e => {
message("cannot update");
})
)
Is there any other way to handle all the logic in the same chain?
It seems like the actual behavior you want can be achieved by reordering your last example:
verifyThing().then(id =>
updateThing(id).catch(e => {
message("cannot update");
})
).catch(e => {
message("cannot verify");
})
The outer catch() will only catch errors from verifyThing() since errors from updateThing(id) have been handled by the inner catch(). In addition, you're always left with a resolved promise instead of a rejected one, which is appropriate since both types of errors have been handled already.
To avoid the appearance that error handling is not close to the source, move the inner portion to a helper function:
function tryUpdateThing (id) {
return updateThing(id).catch(e => {
message("cannot update");
});
}
verifyThing().then(
tryUpdateThing
).catch(e => {
message("cannot verify");
});
Whether or not this is acceptably readable, I'll leave up to you to decide.
If async/await is an option, then:
async function() {
let id;
try {
id = await verifyThing();
await updateThing(id);
} catch(e) {
message(id === undefined ? "cannot verify" : "cannot update");
throw e;
}
}
This assumes that when verifyThing() fulfills, it will resolve with a defined value.

Rethrowing error in promise catch

I found the following code in a tutorial:
promise.then(function(result){
//some code
}).catch(function(error) {
throw(error);
});
I'm a bit confused: does the catch call accomplish anything? It seems to me that it doesn't have any effect, since it simply throws the same error that was caught. I base this on how a regular try/catch works.
There is no point to a naked catch and throw as you show. It does not do anything useful except add code and slow execution. So, if you're going to .catch() and rethrow, there should be something you want to do in the .catch(), otherwise you should just remove the .catch() entirely.
The usual point for that general structure is when you want to execute something in the .catch() such as log the error or clean up some state (like close files), but you want the promise chain to continue as rejected.
promise.then(function(result){
//some code
}).catch(function(error) {
// log and rethrow
console.log(error);
throw error;
});
In a tutorial, it may be there just to show people where they can catch errors or to teach the concept of handling the error, then rethrowing it.
Some of the useful reasons for catching and rethrowing are as follows:
You want to log the error, but keep the promise chain as rejected.
You want to turn the error into some other error (often for easier error processing at the end of the chain). In this case, you would rethrow a different error.
You want to do a bunch of processing before the promise chain continues (such as close/free resources) but you want the promise chain to stay rejected.
You want a spot to place a breakpoint for the debugger at this point in the promise chain if there's a failure.
You want to handle a specific error or set of errors, but rethrow others so that they propagate back to the caller.
But, a plain catch and rethrow of the same error with no other code in the catch handler doesn't do anything useful for normal running of the code.
Both .then() and .catch() methods return Promises, and if you throw an Exception in either handler, the returned promise is rejected and the Exception will be caught in the next reject handler.
In the following code, we throw an exception in the first .catch(), which is caught in the second .catch() :
new Promise((resolve, reject) => {
console.log('Initial');
resolve();
})
.then(() => {
throw new Error('Something failed');
console.log('Do this'); // Never reached
})
.catch(() => {
console.log('Something failed');
throw new Error('Something failed again');
})
.catch((error) => {
console.log('Final error : ', error.message);
});
The second .catch() returns a Promised that is fulfilled, the .then() handler can be called :
new Promise((resolve, reject) => {
console.log('Initial');
resolve();
})
.then(() => {
throw new Error('Something failed');
console.log('Do this'); // Never reached
})
.catch(() => {
console.log('Something failed');
throw new Error('Something failed again');
})
.catch((error) => {
console.log('Final error : ', error.message);
})
.then(() => {
console.log('Show this message whatever happened before');
});
Useful reference : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#Chaining_after_a_catch
Hope this helps!
There is no important difference if you leave out the catch method call completely.
The only thing it adds is an extra microtask, which in practice means you'll notice the rejection of the promise later than is the case for a promise that fails without the catch clause.
The next snippet demonstrates this:
var p;
// Case 1: with catch
p = Promise.reject('my error 1')
.catch(function(error) {
throw(error);
});
p.catch( error => console.log(error) );
// Case 2: without catch
p = Promise.reject('my error 2');
p.catch( error => console.log(error) );
Note how the second rejection is reported before the first. That is about the only difference.
So it sounds like your question is, "In the promise chain, what does the .catch() method do?"
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw
The throw statement "will stop (the statements after throw won't be executed), and control will be passed to the first catch block in the call stack. If no catch block exists among caller functions, the program will terminate."
In the promise chain, the .then() method will return some type of data chunk. This return of the chunk will complete the promise. The successful return of the data completes the promise. You can think of the .catch() method in the same way. .catch() however will handle unsuccessful data retrieves. The throw statement completes the promise. Occasionally, you will see developers use .catch((err) => {console.log(err))} which would also complete the promise chain.
You actually don't need to re throw it, just leave the Promise.catch empty otherwise it will consider as un handle the reject and then wrap the code in a try catch and it will catch the error automatically which is passing down.
try{
promise.then(function(result){
//some code
}).catch(function(error) {
//no need for re throwing or any coding. but leave this as this otherwise it will consider as un handled
});
}catch(e){
console.log(e);
//error can handle in here
}
In the promise chain, it is better to use .catch
ex in function f2: .then(...).catch(e => reject(e));
test1 - with try catch
test2 - without try or .catch
test3 - with .catch
function f1() {
return new Promise((resolve, reject) => {
throw new Error('test');
});
}
function f2() {
return new Promise((resolve, reject) => {
f1().then(value => {
console.log('f1 ok ???');
}).catch(e => reject(e));
});
}
function test1() {
console.log('test1 - with try catch - look in F12');
try {
f2().then(() => { // Uncaught (in promise) Error: test
console.log('???'); });
} catch (e) {
console.log('this error dont catched');
}
}
function test2() {
console.log('test2 - without try or .catch - look in F12');
f2(); // Uncaught (in promise) Error: test
}
function test3() {
console.log('test3 - with .catch');
f2().then(value => {
console.log('??');
}).catch(e => {
console.log(' now its ok, error ', e);
})
}
setTimeout(() => { test1();
setTimeout(() => { test2();
setTimeout(() => { test3();
}, 100);
}, 100);
}, 100);

JavaScript Promises - reject vs. throw

I have read several articles on this subject, but it is still not clear to me if there is a difference between Promise.reject vs. throwing an error. For example,
Using Promise.reject
return asyncIsPermitted()
.then(function(result) {
if (result === true) {
return true;
}
else {
return Promise.reject(new PermissionDenied());
}
});
Using throw
return asyncIsPermitted()
.then(function(result) {
if (result === true) {
return true;
}
else {
throw new PermissionDenied();
}
});
My preference is to use throw simply because it is shorter, but was wondering if there is any advantage of one over the other.
There is no advantage of using one vs the other, but, there is a specific case where throw won't work. However, those cases can be fixed.
Any time you are inside of a promise callback, you can use throw. However, if you're in any other asynchronous callback, you must use reject.
For example, this won't trigger the catch:
new Promise(function() {
setTimeout(function() {
throw 'or nah';
// return Promise.reject('or nah'); also won't work
}, 1000);
}).catch(function(e) {
console.log(e); // doesn't happen
});
Instead you're left with an unresolved promise and an uncaught exception. That is a case where you would want to instead use reject. However, you could fix this in two ways.
by using the original Promise's reject function inside the timeout:
new Promise(function(resolve, reject) {
setTimeout(function() {
reject('or nah');
}, 1000);
}).catch(function(e) {
console.log(e); // works!
});
by promisifying the timeout:
function timeout(duration) { // Thanks joews
return new Promise(function(resolve) {
setTimeout(resolve, duration);
});
}
timeout(1000).then(function() {
throw 'worky!';
// return Promise.reject('worky'); also works
}).catch(function(e) {
console.log(e); // 'worky!'
});
Another important fact is that reject() DOES NOT terminate control flow like a return statement does. In contrast throw does terminate control flow.
Example:
new Promise((resolve, reject) => {
throw "err";
console.log("NEVER REACHED");
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));
vs
new Promise((resolve, reject) => {
reject(); // resolve() behaves similarly
console.log("ALWAYS REACHED"); // "REJECTED" will print AFTER this
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));
Yes, the biggest difference is that reject is a callback function that gets carried out after the promise is rejected, whereas throw cannot be used asynchronously. If you chose to use reject, your code will continue to run normally in asynchronous fashion whereas throw will prioritize completing the resolver function (this function will run immediately).
An example I've seen that helped clarify the issue for me was that you could set a Timeout function with reject, for example:
new Promise((resolve, reject) => {
setTimeout(()=>{reject('err msg');console.log('finished')}, 1000);
return resolve('ret val')
})
.then((o) => console.log("RESOLVED", o))
.catch((o) => console.log("REJECTED", o));
The above could would not be possible to write with throw.
try{
new Promise((resolve, reject) => {
setTimeout(()=>{throw new Error('err msg')}, 1000);
return resolve('ret val')
})
.then((o) => console.log("RESOLVED", o))
.catch((o) => console.log("REJECTED", o));
}catch(o){
console.log("IGNORED", o)
}
In the OP's small example the difference in indistinguishable but when dealing with more complicated asynchronous concept the difference between the two can be drastic.
TLDR: A function is hard to use when it sometimes returns a promise and sometimes throws an exception. When writing an async function, prefer to signal failure by returning a rejected promise
Your particular example obfuscates some important distinctions between them:
Because you are error handling inside a promise chain, thrown exceptions get automatically converted to rejected promises. This may explain why they seem to be interchangeable - they are not.
Consider the situation below:
checkCredentials = () => {
let idToken = localStorage.getItem('some token');
if ( idToken ) {
return fetch(`https://someValidateEndpoint`, {
headers: {
Authorization: `Bearer ${idToken}`
}
})
} else {
throw new Error('No Token Found In Local Storage')
}
}
This would be an anti-pattern because you would then need to support both async and sync error cases. It might look something like:
try {
function onFulfilled() { ... do the rest of your logic }
function onRejected() { // handle async failure - like network timeout }
checkCredentials(x).then(onFulfilled, onRejected);
} catch (e) {
// Error('No Token Found In Local Storage')
// handle synchronous failure
}
Not good and here is exactly where Promise.reject ( available in the global scope ) comes to the rescue and effectively differentiates itself from throw. The refactor now becomes:
checkCredentials = () => {
let idToken = localStorage.getItem('some_token');
if (!idToken) {
return Promise.reject('No Token Found In Local Storage')
}
return fetch(`https://someValidateEndpoint`, {
headers: {
Authorization: `Bearer ${idToken}`
}
})
}
This now lets you use just one catch() for network failures and the synchronous error check for lack of tokens:
checkCredentials()
.catch((error) => if ( error == 'No Token' ) {
// do no token modal
} else if ( error === 400 ) {
// do not authorized modal. etc.
}
There's one difference — which shouldn't matter — that the other answers haven't touched on, so:
If the fulfillment handler passed to then throws, the promise returned by that call to then is rejected with what was thrown.
If it returns a rejected promise, the promise returned by the call to then is resolved to that promise (and will ultimately be rejected, since the promise it's resolved to is rejected), which may introduce one extra async "tick" (one more loop in the microtask queue, to put it in browser terms).
Any code that relies on that difference is fundamentally broken, though. :-) It shouldn't be that sensitive to the timing of the promise settlement.
Here's an example:
function usingThrow(val) {
return Promise.resolve(val)
.then(v => {
if (v !== 42) {
throw new Error(`${v} is not 42!`);
}
return v;
});
}
function usingReject(val) {
return Promise.resolve(val)
.then(v => {
if (v !== 42) {
return Promise.reject(new Error(`${v} is not 42!`));
}
return v;
});
}
// The rejection handler on this chain may be called **after** the
// rejection handler on the following chain
usingReject(1)
.then(v => console.log(v))
.catch(e => console.error("Error from usingReject:", e.message));
// The rejection handler on this chain may be called **before** the
// rejection handler on the preceding chain
usingThrow(2)
.then(v => console.log(v))
.catch(e => console.error("Error from usingThrow:", e.message));
If you run that, as of this writing you get:
Error from usingThrow: 2 is not 42!
Error from usingReject: 1 is not 42!
Note the order.
Compare that to the same chains but both using usingThrow:
function usingThrow(val) {
return Promise.resolve(val)
.then(v => {
if (v !== 42) {
throw new Error(`${v} is not 42!`);
}
return v;
});
}
usingThrow(1)
.then(v => console.log(v))
.catch(e => console.error("Error from usingThrow:", e.message));
usingThrow(2)
.then(v => console.log(v))
.catch(e => console.error("Error from usingThrow:", e.message));
which shows that the rejection handlers ran in the other order:
Error from usingThrow: 1 is not 42!
Error from usingThrow: 2 is not 42!
I said "may" above because there's been some work in other areas that removed this unnecessary extra tick in other similar situations if all of the promises involved are native promises (not just thenables). (Specifically: In an async function, return await x originally introduced an extra async tick vs. return x while being otherwise identical; ES2020 changed it so that if x is a native promise, the extra tick is removed where there is no other difference.)
Again, any code that's that sensitive to the timing of the settlement of a promise is already broken. So really it doesn't/shouldn't matter.
In practical terms, as other answers have mentioned:
As Kevin B pointed out, throw won't work if you're in a callback to some other function you've used within your fulfillment handler — this is the biggie
As lukyer pointed out, throw abruptly terminates the function, which can be useful (but you're using return in your example, which does the same thing)
As Vencator pointed out, you can't use throw in a conditional expression (? :), at least not for now
Other than that, it's mostly a matter of style/preference, so as with most of those, agree with your team what you'll do (or that you don't care either way), and be consistent.
An example to try out. Just change isVersionThrow to false to use reject instead of throw.
const isVersionThrow = true
class TestClass {
async testFunction () {
if (isVersionThrow) {
console.log('Throw version')
throw new Error('Fail!')
} else {
console.log('Reject version')
return new Promise((resolve, reject) => {
reject(new Error('Fail!'))
})
}
}
}
const test = async () => {
const test = new TestClass()
try {
var response = await test.testFunction()
return response
} catch (error) {
console.log('ERROR RETURNED')
throw error
}
}
test()
.then(result => {
console.log('result: ' + result)
})
.catch(error => {
console.log('error: ' + error)
})
The difference is ternary operator
You can use
return condition ? someData : Promise.reject(new Error('not OK'))
You can't use
return condition ? someData : throw new Error('not OK')

Categories

Resources