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.
In the code below the error is never caught:
const fn = () => {
try {
setTimeout(() => { throw new Error('An exception is raised') }, 10)
} catch (error) {
console.error({ error })
}
}
fn()
The following is a solution:
const fn2 = () => {
const errorFn = (error) => console.error({ error })
setTimeout(() => {
try {
throw new Error('An exception is raised')
} catch (e) { errorFn(e) }
}, 10)
}
fn2()
The downside to this solutions is that it has to be implement in the function within setTimeout. That's fine if one controls the code, but if users are supplying the code and calling setTimeout and don't implement appropriate error handling, it could bring down one's server!
Another solution is process.on('uncaughtException,... but that loses the context of the originating sync call that initiated the async function. Unless there is some clever way to supply that context?
Are there any other ways to catch async errors with context to the originating sync code?
Could one set a default error handler for a particular async branch - that catches all unhandled errors that may occur in that async branch?
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.
I'm trying to implement dummy emulation of following logic:
But I'm not sure if I fully understand best practices of how to do it.
The main point of this task is to avoid triggering of redundant catch blocks callbacks. IMO if 1st request failed then all following code should stop.
I mean: if 1st request was failed, then we do not make 2nd request and do not call catch block of 2nd request promise.
In a few words I'm looking for very clean and simple solution like following:
firstRequest()
.then(r => {
console.log('firstRequest success', r);
return secondRequest();
}, e => console.log('firstRequest fail', e))
.then(r => {
console.log('secondRequest success', r);
// Should I return something here? Why?
}, e => console.log('secondRequest fail', e));
I've written following implementation. It works as expected in case of both requests are succeeded, and if 2nd request fails. But it works wrong if 1st request is failed (as you can see both catch block are triggering). You can play around with isFirstSucceed and isSecondSucceed flags to check it.
var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;
var getUsersId = () => new Promise((res, rej) => {
console.log('request getUsersId');
setTimeout(() => {
if (isFirstSucceed) {
return res([1,2,3,4,5]);
} else {
return rej(new Error());
}
}, ms);
});
var getUserById = () => new Promise((res, rej) => {
console.log('request getUserById');
setTimeout(() => {
if (isSecondSucceed) {
return res({name: 'John'});
} else {
return rej(new Error());
}
}, ms);
});
getUsersId()
.then(r => {
console.info('1st request succeed', r);
return getUserById();
}, e => {
console.error('1st request failed', e);
throw e;
})
.then(
r => console.info('2nd request succeed', r),
e => {
console.error('2nd request failed', e);
throw e;
});
I can move then of 2nd request to then of 1st request but it looks ugly.
var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;
var getUsersId = () => new Promise((res, rej) => {
console.log('request getUsersId');
setTimeout(() => {
if (isFirstSucceed) {
return res([1,2,3,4,5]);
} else {
return rej(new Error());
}
}, ms);
});
var getUserById = () => new Promise((res, rej) => {
console.log('request getUserById');
setTimeout(() => {
if (isSecondSucceed) {
return res({name: 'John'});
} else {
return rej(new Error());
}
}, ms);
});
getUsersId()
.then(r => {
console.info('1st request succeed', r);
getUserById().then(
r => console.info('2nd request succeed', r),
e => {
console.error('2nd request failed', e);
throw e;
});
}, e => {
console.error('1st request failed', e);
throw e;
})
Questions:
How to implement described logic according to all promises best practices?
Is it possible to avoid throw e in every catch block?
Should I use es6 Promises? Or it is better to use some promises library?
Any other advices?
Your flow diagram is the logic you want to achieve, but it isn't quite how promises work. The issue is that there is no way to tell a promise chain to just "end" right here and don't call any other .then() or .catch() handlers later in the chain. If you get a reject in the chain and leave it rejected, it will call the next .catch() handler in the chain. If you handle the rejection locally and don't rethrow it, then it will call the next .then() handler in the chain. Neither of those options matches your logic diagram exactly.
So, you have to mentally change how you think about your logic diagram so that you can use a promise chain.
The simplest option (what is probably used for 90% of promise chains) is to just put one error handler at the end of the chain. Any error anywhere in the chain just skips to the single .catch() handler at the end of the chain. FYI, in most cases, I find the code more readable with .catch() than the 2nd argument to .then() so that's how I've shown it here
firstRequest().then(secondRequest).then(r => {
console.log('both requests successful');
}).catch(err => {
// An error from either request will show here
console.log(err);
});
When you provide a catch block and you don't either return a rejected promise or rethrow the error, then the promise infrastructure thinks you have "handled" the promise so the chain continues as resolved. If you rethrow the error, then the next catch block will fire and any intervening .then() handlers will be skipped.
You can make use of that to catch an error locally, do something (like log it) and then rethrow it to keep the promise chain as rejected.
firstRequest().catch(e => {
console.log('firstRequest fail', e));
e.logged = true;
throw e;
}).then(r => {
console.log('firstRequest success', r);
return secondRequest();
}).then(r => {
console.log('secondRequest success', r);
}).catch(e => {
if (!e.logged) {
console.log('secondRequest fail', e));
}
});
Or, a version that marks the error object with a debug message and then rethrows and can then only logs errors in one place:
firstRequest().catch(e => {
e.debugMsg = 'firstRequest fail';
throw e;
}).then(r => {
console.log('firstRequest success', r);
return secondRequest().catch(e => {
e.debugMsg = 'secondRequest fail';
throw e;
});
}).then(r => {
console.log('secondRequest success', r);
}).catch(e => {
console.log(e.debugMsg, e);
});
I've even had situations where a little helper function saved me some code and some visual complexity, particularly if there are a bunch of these in the chain:
function markErr(debugMsg) {
return function(e) {
// mark the error object and rethrow
e.debugMsg = debugMsg;
throw e;
}
}
firstRequest()
.catch(markErr('firstRequest fail'))
.then(r => {
console.log('firstRequest success', r);
return secondRequest().catch(markErr('secondRequest fail'));
}).then(r => {
console.log('secondRequest success', r);
}).catch(e => {
console.log(e.debugMsg, e);
});
Taking each of your questions individually:
How to implement described logic according to all promises best practices?
Described above. I'd say the simplest and best practice is the very first code block I show. If you need to make sure when you get to the final .catch() that you have a uniquely identifiable error so you know which step caused it, then modify the rejected error in each individual function to be unique so you can tell which it was from the one .catch() block at the end. If you can't modify those functions, then you can wrap them with a wrapper that catches and marks their error or you can do that inline with the markErr() type solution I showed. In most cases, you just need to know there was an error and not the exact step it occurred in so usually that isn't necessary for every step in the chain.
Is it possible to avoid throw e in every catch block?
That depends. If the error objects are already unique, then you can just use one .catch() at the end. If the error objects are not unique, but you need to know which exact step failed, then you have to either use a .catch() at each step so you can mark the error uniquely or you need to modify each function in the chain to have a unique error.
Should I use es6 Promises?
Yes. No better way I know of.
Or it is better to use some promises library?
I'm not aware of any features in a promise library that would make this simpler. This is really just about how you want to report errors and whether each step is defining a unique error or not. A promise library can't really do that for you.
Any other advice?
Keep learning more about how to mold promises into a solution for each individual problem.
IMO, you can use async/await... Still, with promises but is much cleaner to look at. Here is my sample approach on above logic.
function firstRequest() {
return new Promise((resolve, reject) => {
// add async function here
// and resolve("done")/reject("err")
});
}
function secondRequest() {
return new Promise((resolve, reject) => {
// add async function here
// and resolve("done")/reject("err")
});
}
async function startProgram() {
try {
await firstRequest();
await secondRequest();
} catch(err) {
console.log(err);
goToEndFn();
}
}
startProgram(); // start the program
https://github.com/xobotyi/await-of
$ npm i --save await-of
import of from "await-of";
async () => {
let [res1, err1] = await of(axios.get('some.uri/to/get11'));
let [res2, err2] = await of(axios.get('some.uri/to/get22'));
if (err1) {
console.log('err1', err1)
}
if (err2) {
console.log('err2', err2)
}
console.log('res1', res1)
console.log('res2', res2)
};
Async/await maybe?
async function foo() {
try {
const firstResult = await firstRequest();
const secondResult = await secondRequest();
} catch(e) {
// e = either first or second error
}
}
In this code an error on the first request transfers control to the catch block and the second request won't start
Should I use es6 Promises?
Probably yes, until you're pretty sure your code will be used in obsolete environments. They are already not so new and flashy
you do not need handle error for every promise
you need handle error only as common error
do like this:
var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;
var getUsersId = () => new Promise((res, rej) => {
console.log('request getUsersId');
setTimeout(() => {
if (isFirstSucceed) {
return res([1,2,3,4,5]);
} else {
return rej();
}
}, ms);
});
var getUserById = () => new Promise((res, rej) => {
console.log('request getUserById');
setTimeout(() => {
if (isSecondSucceed) {
return res({name: 'John'});
} else {
return rej(new Error());
}
}, ms);
});
getUsersId()
.then(r => {
console.info('1st request succeed', r);
return getUserById();
})
.then(r => {
console.info('2st request succeed', r);
return;
})
.catch((e) => {
console.error('request failed', e);
throw new Error(e);
})
You can abuse duck-typing technique to stop promise chain with return { then: function() {} };. I modified your code just right after this line console.error('1st request failed', e);
var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;
var getUsersId = () => new Promise((res, rej) => {
console.log('request getUsersId');
setTimeout(() => {
if (isFirstSucceed) {
return res([1,2,3,4,5]);
} else {
return rej(new Error());
}
}, ms);
});
var getUserById = () => new Promise((res, rej) => {
console.log('request getUserById');
setTimeout(() => {
if (isSecondSucceed) {
return res({name: 'John'});
} else {
return rej(new Error());
}
}, ms);
});
getUsersId()
.then(r => {
console.info('1st request succeed', r);
return getUserById();
}, e => {
console.error('1st request failed', e);
return { then: function() {} };
})
.then(
r => console.info('2nd request succeed', r),
e => {
console.error('2nd request failed', e);
throw e;
});
I have a simple async function. It just sends a request and returns the data:
export const updatePanorama = async ({ commit }, payload) => {
const urlEnd = '/v1/pano/update'
const type = 'post'
const resp = await api.asyncRequest(urlEnd, type, payload)
commit('SET_PANORAMA', resp.data)
return resp
}
And this is how I'm using the function:
handleUpdatePanorama (panorama) {
const payload = {}
this.updatePanorama(payload).then(resp => {
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
}).catch(() => {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
},
The problem is, the code after catch runs if there's an error inside then. But this way I don't know whether the catch error is an request error or and error triggered by the code inside then.
I'm trying try and catch to solve that problem:
handleUpdatePanorama (panorama) {
try {
const payload = {}
const resp = await this.updatePanorama(payload)
console.log('resp:', resp)
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
} catch (err) {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
},
However, I get an unexpected token error in this line: await this.updatePanorama(payload)
What am I doing wrong?
The problem is, the code after catch runs if there's an error inside then
The solution for that is to not use catch, but the second then parameter. Have a look at the difference between .then(…).catch(…) and .then(…, …) for details.
I'm trying try and catch to solve that problem
That won't work, the catch clause will still be called if there's an exception thrown by setIsLoading or handleAlert.
I get an unexpected token error. What am I doing wrong?
You have not declared the handleUpdatePanorama method as async.
To mitigate the issues and fix the syntax, you could write
async handleUpdatePanorama (panorama) {
var result
try {
const payload = {}
const resp = await this.updatePanorama(payload)
console.log('resp:', resp)
result = ['updateSuccess', 'success']
} catch (err) {
result = ['updateError', 'danger']
} finally {
this.setIsLoading(false)
}
this.handleAlert(...result)
},
If you need to handle errors specifically from updatePanorama, use the second argument to .then(onSuccess, onError)
handleUpdatePanorama(panorama) {
const payload = {}
this.updatePanorama(payload).then(resp => {
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
}, err => {
// handle error from updatePanorama
// you can throw err if you also want to handle the error in .catch()
}).catch(() => {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
}
note: if you return (or have no return statement) from the error handler, any subsequent .then(onSuccess will execute, if you throw an error (or return Promise.reject() for example, then the .catch() code will also run