I have a store method...
async store() {
try {
return await axios.post('/upload', data);
} catch (error) {
}
},
Called by:
store().then(()=>{ console.log('ok'); }, ()=>{ console.log('not ok'); });
But when the store method fails and an error is caught, the first method in then is always called, how can I get the failed not ok method to be called?
You need to throw the error caught in the catch block of the store function
async store() {
try {
return await axios.post('/upload', data);
} catch (error) {
throw error;
}
}
You could also skip catching the error in the store function and simply catch it when store function is called. To do this, you just need to return the result of axios.post(...).
async store() {
return axios.post('/upload', data);
}
(Note that, without the try-catch block, you don't need an await before axios.post(...) because the promise returned by the store function will be resolved to the promise returned by axios.post(...). This means that if the promise returned by axios.post(...) is fulfilled, promise returned by the store function will also fulfil with the same fulfilment value with which the promise returned by axios.post(...) fulfilled.)
It is uncommon to pass second argument to then function. Instead, you should chain a .catch() block to catch the error.
store()
.then(() => console.log('ok'))
.catch(() => console.log('not ok'));
Related
I am using await to make the code cleaner, but I am not sure whether I am handling exceptions correctly.
An example while using azure-devops-node-api;
const foo = async() => {
return new Promise((resolve, reject) => {
...
...
const teams = await coreApiObject.getTeams(currProject.id)
.catch(err => { reject(err) return })
...
...
})
}
In this code I am assuming, if there is a problem with promise call, foo() is going to return reject.
async functions always return a promise, so you don't need to explicitly create one yourself. Any non-promise value returned from an async function is implicitly wrapped in a promise.
Inside the foo function, you just need to await the call coreApiObject.getTeams(...) and to catch and handle any error, use the try-catch block.
Your code can be simplified as shown below:
const foo = async() => {
try {
const teams = await coreApiObject.getTeams(currProject.id);
return teams;
} catch (e) {
// handle error
}
}
If you want to the calling code to handle the error, then you can use one of the following options:
Remove the try-catch block and just return the result of coreApiObject.getTeams(...).
const foo = async() => {
return coreApiObject.getTeams(currProject.id);
}
Removing the try-catch block and just returning the call to coreApiObject.getTeams(...) will allow the calling code to handle the error because the promise returned by the foo function will get resolved to the promise returned by coreApiObject.getTeams(...); this means that the fate of the promise returned by the foo function will depend on whatever happens to the promise returned by coreApiObject.getTeams(...).
If the promise returned by coreApiObject.getTeams(...) is rejected, promise returned by the foo function will also be rejected and hence the calling code will have a change to catch the promise rejection and handle it.
Throw the error from the catch block.
const foo = async() => {
try {
const teams = await coreApiObject.getTeams(currProject.id);
return teams;
} catch (error) {
// throw the error
throw error;
}
}
Other option is to throw the error from the catch block to make sure that the promise returned by the async function is rejected.
If you don't throw the error or return a promise or a thenable that is rejected, returning any other value from the catch block will fulfil the promise returned by the async function with whatever value is returned inside the catch block.
I have the following code:
loggerManager.js:
export default log(message) {
try {
axios.post(...).catch(error) { console.log(error); }
}catch(error) {
console.log(error);
}
}
loggerManager.test.js:
test('', () => {
jest.spyOn(global.console, 'log');
jest.mock('axios');
axios.post = jest.fn(() => Promise.reject('fail'));
log('message');
expect(console.log).toBeCalledWith('fail'); // The result said it has not been called even once
});
Where am I going wrong?
Two issues:
A rejection handler converts rejection into fulfillment if it doesn't throw or return a promise that is rejected
try/catch doesn't catch promise rejections except when the promise is consumed via await within the try block
So assuming the normal case, if your axios.post's promise rejects, the rejection handler runs but the catch block does not.
In this code:
export default log(message) {
try {
axios.post(...).catch(error) { console.log(error); }
}catch(error) {
console.log(error);
}
}
Execution will only go into the catch block at the end if:
axios is an undeclared identifier; or
axios doesn't have a post property; or
axios.post isn't a function; or
axios.post throws when called; or
axios.post doesn't return an object with a catch property; or
The catch property of the object axios.post returns isn't a function; or
The catch method of the object axios.post returns throws when called
It will not be entered if axios.post returns a promise that is rejected.
You may have wanted this:
export default async log(message) {
// −−−−−−−−−−−−^^^^^
try {
await axios.post(...);
// −^^^^^
}catch(error) {
console.log(error);
}
}
That way, the catch block will be entered if there was a problem calling axios.post or if axios.post returns a promise that is rejected.
function sendPushNotification(subscription, urlEncodedData){
try {
webpush.sendNotification(subscription, urlEncodedData);
} catch (err){
console.log('err');
console.log(err);
}
}
This doesn't catch the error, it is still considered an 'unhandledRejection'. Why?
If you're calling an async function, or a function that returns a Promise then you need to sequence things properly:
async function sendPushNotification(subscription, urlEncodedData){
try {
await webpush.sendNotification(subscription, urlEncodedData);
} catch (err){
console.log('err');
console.log(err);
}
}
Here await will capture any response. An error condition that will manifest as an exception.
Note that this makes sendPushNotification() return a Promise so you will have to treat it as asynchronous. This means the caller needs to await as well, and that may have impacts up the call chain depending on your sequencing requirements.
I am hard time writing test to assert something happened inside catch block which is executed inside forEach loop.
Prod code
function doSomething(givenResourceMap) {
givenResourceMap.forEach(async (resourceUrl) => {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
});
I am wanting to assert logger.error is being called twice and called with right arguments each time. So I wrote some test like this
describe('Do Something', () => {
it('should log message if fail to delete the resource', function() {
const resource1Url = chance.url();
const givenResourceMap = new Map();
const thrownError = new Error('Failed to delete');
givenResourceMap.set(resource1Url);
sinon.stub(logger, 'error');
sinon.stub(axios, 'delete').withArgs(resource1Url).rejects(thrownError);
await doSomething(givenResourceMap);
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
// Also need to know how to assert about `throw error;` line
});
});
I am using Mocha, sinon-chai, expect tests. Above test is failing saying logger.error is being 0 times.
Thanks.
The problem is that you are using await on a function that doesn't return a Promise. Note that doSomething is not async and does not return a Promise object.
The forEach function is async but that means they'll return right away with an unresolved Promise and you don't ever await on them.
In reality, doSomething will return before the work inside of the forEach is complete, which is probably not what you intended. To do that you could use a regular for-loop like this:
async function doSomething(givenResourceMap) {
for (const resourceUrl of givenResourceMap) {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
}
}
Note that it changes the return type of doSomething to be a Promise object rather than just returning undefined as it originally did. But it does let you do an await on it as you want to in the test (and presumably in production code also).
However since you re-throw the exception caught in the loop, your test will exit abnormally. The test code would have to also change to catch the expected error:
it('should log message if fail to delete the resource', function(done) {
// ... the setup stuff you had before...
await doSomething(givenResourceMap).catch(err => {
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
done();
});
});
I'm trying to do some validations before creating/updating an entry as shown below:
async save(){
return new Promise((resolve, reject)=>{
if(!this.isCampaignValid){
this.handleError()
reject()
}
else{
this.$store
.dispatch('updateCampaign')
.then((res)=>{
resolve()
this.showNotification(res.message, 'success')
})
.catch(error=>{
this.showNotification(error.message, 'error')
reject()
})
}
})
},
the isCampaignValid is a computed value which computes the validity.
If the campaign is not valid, then I'm getting an error in the console as below:
Uncaught (in promise) undefined
The this.handleError() function works too. How can handle this promise error situation?
Just in case handleError() throws, try:
if (!this.isCampaignValid) {
try {
this.handleError()
} catch (e) {
console.error(e);
}
reject()
}
First of all, you don't need to return a promise in an async function. It implicitly returns one, resolving with the value returned by the function or rejecting with the error object if the function throws. Although you could return a promise and JS unpacks it for you, it's unneeded code.
That said, because async returns a promise, you'll have to catch that promise too. Since your first conditional block just throws an error but doesn't catch it, the promise returned by save will reject. You need to handle that rejection.
Here's a simplified version of your code to see where it's happening.
async save(){
if(!this.isCampaignValid){
this.handleError()
// Throwing an error in an async function is equivalent to a reject.
throw new Error('Campaign is not valid') // Here
}
else{
try {
const res = await this.$store.dispatch('updateCampaign')
this.showNotification(res.message, 'success')
} catch (e) {
this.showNotification(error.message, 'error')
}
}
},
// When you call save, catch the error
yourObject.save()
.then(() => {...})
.catch(() => {...})
// If your call is in an async function, you can try-catch as well
try {
await yourObject.save()
} catch(e) {
// It failed.
}