I am trying to test that a Vuex action's .catch(() block is reached given a certain API response, and that it returns the error. The catch is reached, but the test fails since it is expecting the actual API response, instead of the error that I throw.
The action that I am testing is:
getPageItems ({ commit, state, }) {
const page = state.page;
return testApi.fetch(`${pageNumber}`).then((response) => {
try {
isValid(response);
commit('addItemsToList', response);
} catch (error) {
console.error(error);
}
},
export const isValid = (response) => {
response.name ? true : throw new Error('invalid item');
};
The test I have is:
test('errors caught', async () => {
const item = {};
const commit = jest.fn();
const state = {
pageNumber: 2,
};
testApi.fetch.mockRejectedValue(item);
expect.assertions(1);
await getPageItems({ commit, state, }).catch((e) => expect(e).toBe('invalid item');
});
This test fails, as it expects e to be item (the response), and not the error. I'm not sure why this is the case.
mockApi.get.mockResolvedValue(item) results in fulfilled promise, none of catch callbacks will be called.
catch makes getPageItems unconditionally resolve with fulfilled promise, another catch callback after getPageItems() will never be called. It doesn't cause bad response error either. getPageItems returns a fulfilled promise and conditionally calls console.error, this is what needs to be tested.
This test doesn't return a promise, even if a rejection was asserted, it would be ignored. async..await is the way to chain promises correctly:
test('errors are caught', async () => {
mockApi.get.mockResolvedValue();
jest.spyOn(console, 'error');
await getPageItems({ commit, state });
expect(console.error).toHaveBeenCalledWith('bad response'));
});
Related
class AuthController {
static methods = {
GET: {
'/auth/signup': {
func: AuthService.signUp,
response: (data, res) => {
res.statusCode = 200;
res.end(JSON.stringify(data));
},
},
},
};
static use(req, res) {
const route = this.methods[req.method][req.url];
if (!route) {
res.statusCode = 404;
res.end(JSON.stringify({ message: 'Not found 404!' }));
return;
}
try {
const data = JSON.parse(req?.body?.data || '{}');
const result = route.func({ ...data });
route.response(result, res);
} catch (err) {
console.log(err, 'here');
res.statusCode = err.statusCode || 500;
res.end(JSON.stringify(err.message));
}
}
}
class AuthService {
static async signUp({ login, password }) {
if (!login || !password) throw new BaseError(400, 'kl', 'Custom error');
}
}
It shows the error in console but try catch block doesn't see it.
Here is the traceback.
I don't know what the reason is because the function which throws error is inside of the block. Help please!
The trace back that you attached tells you exactly what the problem is and what you need to do:
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
You can't catch an exception thrown by an async function with a try..catch block outside of that function, because script execution reaches the catch block before the async execution is finished. You therefor have to use .catch(..) instead:
const result = route.func({ ...data }).catch((err) => {
console.log("catched error: ", err);
});
I see one issue. You have declared signUp() to be async. That means it always returns a promise and it means that any throw operations inside it reject that promise that it returns (the exception doesn't propagate synchronously). But, when you attempt to call it here:
const result = route.func({ ...data });
You don't await it so when signUp() rejects, the promise goes into result, but nobody ever handles the fact that the promise rejected and you get UnhandlePromiseRejectionWarning from the system.
I can't see the whole overall design (of all the other routes), but perhaps you just need to add await to this:
const result = await route.func({ ...data });
And, you would have to make .use() be async also.
Or, if signUp() doesn't actually need to be async, then just remove the async from its declaration and the throw will be synchronous (instead of being turned into a rejected promise) and your try/catch will catch it then.
The code below is an example of what may take place during development.
With the current code, the outer function may throw an error but in this case wont. However, the nested function WILL throw an error (for examples sake). Once it throws the error it cannot be caught as it is asynchronous function.
Bungie.Get('/Platform/Destiny2/Manifest/').then((ResponseText)=>{
//Async function that WILL throw an error
Bungie.Get('/Platform/Destiny2/Mnifest/').then((ResponseText)=>{
console.log('Success')
})
}).catch((error)=>{
//Catch all errors from either the main function or the nested function
doSomethingWithError(error)
});
What I want is for the outer most function to catch all asynchronous function error's but with this code I cannot. I have tried awaiting the nested function but there may be certain circumstances where it will be quicker to not wait for the function. I also tried to include a .catch() with each nested function but this would require a .catch() for each function that would allhandle the error in the same way e.g. doSomethingWithError().
you only needs return the inner function in the outside function.
see example below:
const foo = new Promise((resolve,reject) =>{
setTimeout(() => resolve('foo'), 1000);
});
foo.then((res)=>{
console.log(res)
return new Promise((resolve,reject)=>{
setTimeout(() => reject("bar fail"), 1000);
})
}).catch((e)=>{
// your own logic
console.error(e)
});
this is called promise chaining. see this post for more info https://javascript.info/promise-chaining
if you have multiple promises can do something like:
const foo1 = new Promise((resolve,reject) =>{
setTimeout(() => resolve('foo1'), 1000);
});
const foo2 = new Promise((resolve,reject) =>{
setTimeout(() => resolve('foo2'), 2000);
});
const foo3 = new Promise((resolve,reject) =>{
setTimeout(() => reject('foo3'), 3000);
});
const bar = new Promise((resolve,reject) =>{
setTimeout(() => resolve('bar'), 4000);
});
foo1
.then((res)=>{
console.log(res)
return foo2
})
.then((res)=>{
console.log(res)
return foo3 // throws the error
})
.then((res)=>{
console.log(res)
return bar
})
.catch((e)=>{
// every error will be cached here
console.error(e)
});
I would aim to use async / await unless you have very particular reasons, since it avoids callback hell and makes your code simpler and more bug free.
try {
const response1 = await Bungie.Get('/Platform/Destiny2/Manifest/');
const response2 = await Bungie.Get('/Platform/Destiny2/Mnifest/');
console.log('Success');
} catch (error) {
doSomethingWithError(error);
}
Imagine each Bungie call takes 250 milliseconds. While this is occurring, NodeJS will continue to execute other code via its event loop - eg requests from other clients. Awaiting is not the same as hanging the app.
Similarly, this type of code is used in many browser or mobile apps, and they remain responsive to the end user during I/O. I use the async await programming model in all languages these days (Javascript, Java, C#, Swift etc).
Try this:
let getMultiple = function(callback, ... keys){
let result = [];
let ctr = keys.length;
for(let i=0;i<ctr;i++)
result.push(0);
let ctr2 = 0;
keys.forEach(function(key){
let ctr3=ctr2++;
try{
Bungie.Get(key, function(data){
result[ctr3] = data;
ctr--;
if(ctr==0)
{
callback(result);
}
});
} catch(err) {
result[ctr3]=err.message;
ctr--;
if(ctr==0)
{
callback(result);
}
}
});
};
This should get all your data requests and replace relevant data with error message if it happens.
getMultiple(function(results){
console.log(results);
}, string1, string2, string3);
If the error causes by requesting same thing twice asynchronously, then you can add an asynchronous caching layer before this request.
const fetch = require('node-fetch');
let url = 'something.com';
module.exports = function(context) {
let a = fetch(url)
a.then(res => {
if(res.status!=200) throw new Error(res.statusText)
else{
context.done(null, res.body);
}
});
a.catch(err => {
console.log(err)
throw new Error(err)
});
};
I have a durable function that calls an activity function like above. I have set automatic retry on failure on this activity function. To retry the function needs to get an error.
So In get request I want to throw an error when i get response like 404 or something similar. But when i throw from catch block i get an error like below
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This
error originated either by throwing inside of an async function
without a catch block, or by rejecting a promise which was not handled
with .catch().
function pauses there and stops execution.I have to manually stop and start the execution. How can i handle this so that the function retries?
Your code branches.
Ignoring the detail, what you have is :
let a = <Promise>; // root
a.then(...); // branch_1
a.catch(...); // branch_2
So whereas you catch errors arising in a, any error arising in branch 1 will be uncaught. Hence the warning
Compare that with :
let a = <Promise>; // root
a.then(...).catch(...); // branch
or
<Promise>.then(...).catch(...); // no assignment necessary
So, you might write :
module.exports = function(context) {
return fetch(url)
.then(res => {
if(res.status!=200) {
throw new Error(res.statusText);
} else {
context.done(null, res.body);
}
})
.catch(err => {
console.log(err)
throw new Error(err)
});
};
Alternatively, depending on the required division of responsibilities between module and caller(s) ...
module.exports = function(context) {
return fetch(url)
.then(res => {
if(res.status!=200) {
throw new Error(res.statusText);
} else {
return res;
}
});
};
... and call .context.done(null, res.body); in a .then() callback in the caller.
In both cases, with return included, then the caller will need to catch errors otherwise you will again get an unhandled error warning.
Found that with the use of async/await this problem goes away and the function re-try after exception is thrown.
const fetch = require('node-fetch');
let url = 'something.com';
module.exports = async function(context) {
let res = await fetch(url)
if(res.status!=200) throw new Error(res.statusText);
else return res.body;
};
I have the following async function that checks the returned value from a promise and I having trouble writing
async function fetchData(pageLocation) {
const data = await
apiService.fetchPage(pageLocation);
if (!data || !data.mapping) {
const error = new Error(`Unknown channel ${pageLocation}`);
error.code = 404;
throw (error);
}
return data.mapping;
}
Test case
describe.only('fetchData', () => {
let fetchPage;
beforeEach(() => {
fetchPage =
sinon.stub().returns(Promise.resolve(mockMapping));
csfPageService.__set__({
apiService: {
fetchPage,
},
});
});
it('should throw an error when there is no available Data', () => {
channeData', async function() {
const fetchChannelSectionData = pageService.__get__('fetchData');
expect(async () => { await fetchData('pageLocation'); }).to.throw();
expect(fetchPage).to.be.calledWith('pageLocation');
console.log('----------------------2');
});
What causing the main issue is having an async function and a promise I am able to use the same approach when it is not an async function and there is no await I have looked into the following links
Catching thrown errors with SinonJS
https://www.chaijs.com/api/bdd/#method_throw
enter link description here
but I haven't been successful
please advise on how should this be done ...
That is one of the reasons I don't like async, await, they are just syntactic sugar over promises, but they uses normal/sync semantics but just in appearance.
Async functions never throws, no matter how bad is the error you throw inside it, they will just return a rejected promise. In your case, your function is not throwing at all, it is returning a rejected promise, and you are not attaching any catch hanlder to that promise, hence the warning. When you use async function or promises, forget about normal handling of errors, promises catches any error automatically and encapsulates them on a rejected promise.
So, in your case the correc way of doing this will vary depending on your testing framework, but it could be something like this:
it('should throw an error when there is no available Data', () => {
channeData', async function() {
const fetchChannelSectionData = pageService.__get__('fetchData');
fetchData('pageLocation').catch(err => {
expect(err).to.be.an.error();
expect(fetchPage).to.be.calledWith('pageLocation');
console.log('----------------------2');
})
});
For example,
Comments.findOne({user: req.user.id}).exce()
.then(function(comment) {
if(comment) {
// how to make this return immediately and break the rest then?
return res.json({error: 'Already commented'});
} else {
return Posts.findOne({postId: req.params.id}).exec();
}
})
.then(function(post) {
if(post) {
var comment = new Comment({user: req.user.id, data: req.body.comment})
return {post: post, comment: comment.save()};
} else {
return res.json({error: 'Post not exist'});
}
})
.then(function(data) {
post.comments.push(comment._id);
return post.save();
});
.then(function(post) {
return res.json({ok: 1})
})
.catch(function(e)) {
return res.json(error: e);
});
Is this promise written right?
How to write this kind of promise?
Callbacks/Promises is a headache...
You're using bluebird, it supports cancellation. Here's an example:
var Promise = require('bluebird');
// enable cancellation
Promise.config({
cancellation: true
});
// store your promise chain
var myPromise = Promise.resolve().then(() => {
return 'foo';
}).then((res) => {
console.log(res);
// call cancel on the chain when needed
myPromise.cancel();
return res;
}).then((res) => {
// this will not be executed
console.log(res + '2');
});
You just need to throw or return a reject promise to trigger the error handling in promises as show in this example:
Comments.findOne({user: req.user.id}).exce()
.then(function(comment) {
if(comment) {
// to bypass all the other .then() resolve handlers, either
// throw an error here or return a rejected promise
throw new Error(res.json({error: 'Already commented'}));
} else {
return Posts.findOne({postId: req.params.id}).exec();
}
})
Promises are "throw safe" which means that .then() will catch any exception thrown and turn it into a rejected promise. This will bypass any following .then() resolve handlers and will instead go to the next reject handler or .catch() handler.
FYI, you should be careful in your code because when you .catch() and then just return from that, it changes your promise state from rejected to resolved and it will then look like a successful promise when you actually had an error and any caller will think everything was successful. This is because the promise infrastructure assumes that if you .catch() an error and return a value that you have "handled" the error and the state is now resolved successfully. To allow the error to continue to propagate to higher callers from a .catch(), you have to re throw or return a rejected promise:
blah(...).then(...)
.catch(function(e)) {
throw new Error(res.json(error: e));
});