Firebase Functions How To Handle Errors Properly [duplicate] - javascript

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

Related

Find which promise failed in chain of .then

I'm trying to design a chain of promises with a catch error at the end in my node + express app. In the example below, if any one of the 'then' functions error out I'll have to work backwards to find which one from the error message. Is there an easier way to code the catch function?
new Promise(function(resolve, reject) {
resolve(groupId);
})
.then(sid => getGuestGroup2(sid))matching carts
.then(group =>getProducts(group))//add products
.then(result2 =>getPrices(result2))
.catch(error => { // (**)
console.log('Error on GET cart.js: '+error);
res.sendStatus(500);
});
The Promise chaining is generic enough to not have 'which step failed' kind of information included out of the box. You could potentially try to decode it from stack trace, but in my opinion that's way more work than its worth. Here are few options you can employ to determine which step failed.
Option 1
Set an extra property (as indicator) on the error object, which could be decoded in the catch block to determine which step in chain, the error originated from.
function predictibleErrors(promise, errorCode) {
return Promise.resolve(promise)
.catch(err => {
const newErr = new Error(err.message);
newErr.origErr = err;
newErr.errorCode = errorCode;
throw newErr;
});
}
Promise.resolve(groupId)
.then(sid => predictibleErrors(getGuestGroup2(sid), 'STEP_1')) // matching carts
.then(group => predictibleErrors(getProducts(group), 'STEP_2')) // add products
.then(result2 => predictibleErrors(getPrices(result2), 'STEP_3'))
.catch(error => { // (**)
console.log('Error on GET cart.js: '+error);
// Check error.errorCode here to know which step failed.
res.sendStatus(500);
});
Option 2
Good old, catch after every step, and re-throw to skip subsequent steps.
Promise.resolve(groupId)
.then(sid => getGuestGroup2(sid)) // matching carts
.catch(err => {
console.log('step 1 failed');
err.handled = true; // Assuming err wasn't a primitive type.
throw err;
})
.then(group => getProducts(group)) // add products
.catch(err => {
if(err.handled) { return; }
console.log('step 2 failed');
err.handled = true;
throw err;
})
.then(result2 => getPrices(result2))
.catch(err => {
if(err.handled) { return; }
console.log('step 3 failed');
err.handled = true;
throw err;
})
.catch(error => {
// Some step has failed before. We don't know about it here,
// but requisite processing for each failure could have been done
// in one of the previous catch blocks.
console.log('Error on GET cart.js: '+error);
res.sendStatus(500);
});
Please note that what we do in option two here, could also be done by the underlying methods directly. For eg. getGuestGroup2 or getProducts could include an errorCode or similar property on the error object that it throws. In this example we know step 1 or step 2 failed, but cannot know why. If the underlying methods were setting the same, they could include more accurate errorCodes with the knowledge of why the operation failed. I didn't take that route since the methods are not included in your sample and as far as I know they might not be in your control.

ESLint showing me error and suggest to use try...catch

Currently, I was taking a course:Front-End Web Development with React in coursera, and I was stuck at a point where the instructor was showing how to fetch data using cross-fetch. Here he was showing
export const fetchDishes = () => (dispatch) => {
return fetch(baseUrl + 'dishes')
.then(
(response) => {
if (response.ok) {
return response;
} else {
var error = new Error(
'Error ' + response.status + ': ' + response.statusText
);
error.response = response;
throw error;
}
},
//manually handle error if the server didn't response
(error) => {
var errmess = new Error(error.message);
throw errmess;
}
)
.then((response) => response.json())
.then((dishes) => dispatch(addDishes(dishes)));
.catch(error => dispatch(dishesFailed(error.message)));
};
But my ESLint showing me error and suggest to use try...catch block.
image
But I was wondering why this error occurs even though the instructor write it as above and run the application perfectly? I have no idea how to convert this code into a try...catch block.
#erik_m give a solution but I did get that is semicolon mean terminate the promise chain? And one more thing which temp me that the instructor didn't import fetch (like import fetch from 'cross-fetch')then how my application is using fetch? He just showed to do yarn add cross-fetch#2.1.0 Did fetch is inherited by default with react application?
You are terminating the call one line above the catch.
.then((response) => response.json())
.then((dishes) => dispatch(addDishes(dishes)));
.catch(error => dispatch(dishesFailed(error.message)));
on the line right above the .catch, remove the semicolon, and the paren. Your catch is not properly part of your then statement.
Edit: Can you try to replace the last three lines with this? You have your parens and semi colons in the wrong place. So the program thinks you are ending your promise after the second then, so it is not recognizing the "catch" portion.
.then((response) => response.json())
.then((dishes) => dispatch(addDishes(dishes))
.catch(error => dispatch(dishesFailed(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.

Catching promise errors inside another promise's callback

The code below runs as expected. If the charge function is invoked, the function fetches the relevant ticket object from firestore and then returns it back to the client.
If the ticket doesn't exist, the function throws a HttpsError with an error message that will be parsed by the client.
exports.charge = functions.https.onCall(data => {
return admin.firestore().collection('tickets').doc(data.ticketId.toString()).get()
.then((snapshot) => {
return { ticket: snapshot.data() }
})
.catch((err) => {
throw new functions.https.HttpsError(
'not-found', // code
'The ticket wasn\'t found in the database'
);
});
});
The problem comes after this. I now need to charge the user using Stripe, which is another asynchronous process that will return a Promise. The charge requires the pricing info obtained by the first async method, so this needs to be called after snapshot is retrieved.
exports.charge = functions.https.onCall(data => {
return admin.firestore().collection('tickets').doc(data.ticketId.toString()).get()
.then((snapshot) => {
return stripe.charges.create(charge) // have removed this variable as irrelevant for question
.then(() => {
return { success: true };
})
.catch(() => {
throw new functions.https.HttpsError(
'aborted', // code
'The charge failed'
);
})
})
.catch(() => {
throw new functions.https.HttpsError(
'not-found', // code
'The ticket wasn\'t found in the database'
);
});
});
My problem is with catching errors in the new charge request. It seems that if the charge fails, it successfully calls the first 'aborted' catch, but then it is passed to parent catch, and the error is overridden and the app sees the 'ticket not found' error.
How can I stop this from happening? I need to catch both errors separately and throw a HttpsError for each one.
Generally, such problems are can be handled with adding status node and then chaining with a final then block. You can try something like following
exports.charge = functions.https.onCall(data => {
return admin.firestore().collection('tickets').doc(data.ticketId.toString()).get()
.then((snapshot) => {
return stripe.charges.create(charge)
.then(() => {
return { success: true };
})
.catch(() => {
return {
status : 'error',
error : new functions.https.HttpsError(
'aborted', // code
'The charge failed',
{ message: 'There was a problem trying to charge your card. You have NOT been charged.' }
)};
})
})
.catch(() => {
return {
status : 'error',
error : new functions.https.HttpsError(
'not-found', // code
'The ticket wasn\'t found in the database',
{ message: 'There was a problem finding that ticket in our database. Please contact support if this problem persists. You have NOT been charged.' }
)};
}).then((response) => {
if(response.status === 'error') throw response.error;
else return response;
});
});
Don't nest a then inside another then for multiple items of work:
work1
.then((work1_results) => {
return work2.then((work2_results) => {
// this is bad
})
})
Instead, perform all your work as a chained sequence:
work1
.then((work1_results) => {
return work2
})
.then((work2_results) => {
// handle the results of work2 here
})
You can store intermediate results in higher-scoped variables if you need to accumulate data between your callbacks.

Breaking promise chain with thrown exception

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);
});
}

Categories

Resources