//foo.js
const api = require('someApi');
exports.get = obj => {
if (!obj.id) {
return Promise.reject('no id');
}
api.get(obj.id)...
}
//foo.spec.js
let getStub;
beforeEach(() => {
getStub = sinon.stub(api, 'get');
});
it('should fail to retrieve location on insufficient data', () => {
let obj = {};
foo.get(obj)
.catch(err => {
getStub.should.have.not.been.called();
expect(err).to.not.equal('undefined');
})
});
When i execute the test I receive this error:
(node:73773) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): TypeError: Cannot read property 'have' of undefined
There is no other stack trace to the error. As far as i can see I have handling the promise rejection by catching the error in the catch block.
Is this a good way to test and how should I fix this ?
This is usually because the "it" block finished before the catch block catches the exception. You can add a 'done' call to prevent this:
it('should fail to retrieve location on insufficient data', (done) =>
{
let obj = {};
foo.get(obj)
.catch(err => {
getStub.should.have.not.been.called();
expect(err).to.not.equal('undefined');
done();
})
}
Related
My unit test code is like this:
// function for my unit test to test exception
const mockServiceThrow = async () => { throw new Error('unit test error message'); };
const createContextAndDoc = () => new Promise((resolve, reject) => {
(async () => {
const res = await mockServiceThrow();
if (res === 1) resolve(1)
else reject(0);
})();
});
createContextAndDoc().catch((e) => {
console.log('--------');
console.log(e.message);
console.log('--------');
});
When i run this unit test:
./node_modules/.bin/jest local_modules/__test__/unhandledException.test.js
The complete output is like this:
RUNS local_modules/__test__/unhandledException.test.js
node:internal/process/promises:246
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: 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(). The promise rejected with the reason "Error: unit test error message".] {
code: 'ERR_UNHANDLED_REJECTION'
}
Don't see why it says unhandled, i do have .catch(). Any suggestions ?
Looks like you're invoking the async function right away, which causes the unit test error, and that is never cought since the promise is never returned - so the .catch is not catching anything. The error is not part of the promise chain.
If you want to invoke the function right away you need to catch the error and reject it so the promise finishes.
const mockServiceThrow = async () => { throw new Error('unit test error message'); };
const createContextAndDoc = () => new Promise((resolve, reject) => {
(async () => {
try {
const res = await mockServiceThrow();
if (res === 1) resolve(1)
else reject(0);
} catch (e) {
reject(e)
}
})();
});
createContextAndDoc().catch(e => {
console.log('------------------');
console.log(e.message);
console.log('--------')
})
You could also simplify your code a little:
const mockServiceThrow = async () => { throw new Error('unit test error message'); };
const createContextAndDoc = async () => {
const res = await mockServiceThrow();
if(res === 1) {
return Promise.resolve(1)
} else {
return Promise.reject(0);
}
};
createContextAndDoc().catch(e => {
console.log('------------------');
console.log(e.message);
console.log('--------')
})
Edit: Further explanation of promises.
createContextAndDoc is a function that returns a promise. That promise resolves if res === 1 but rejects if res is something else. For that to happen the mockServiceThrow promised must be resolved. Otherwise you won't get any value for res variable.If that happens then your promise function never fullfills (resolves or rejects).
In your case mockServiceThrow fails and throws an error, this error is not part of the promise you created with new Promis. To make sure your promise fullfills (resolves or rejects) you need the callbacks, otherwise the error is not part of the promise.
The simplified code has one async function so that when mockServiceThrow fails its part of the async function that you're trying to catch.
I have an existing Node Express Application and want to improve the error handling better. My current route endpoint definition is like below,
app.get('/example/getActiveId', async (req, res, next) => {
// Some code to fetch some details from request and do some validations
try {
const result = await api.getActiveId(id);
res.json({ success: true, result }); // I am getting this response in all the time.
} catch (err) {
console.log('getActiveId', err)
console.error(err);
res.json({ success: false });
}
});
Also, I defined error middleware at the last of all the route paths.
// error handler middleware
app.use((error, req, res, next) => {
console.log('in Error middleware')
console.error(error.stack);
res.status(500).send(error.message || 'Something Broke!');
})
My definition of getActiveId is as below.
exports.getActiveId = id => axiosInstance
.get('/example')
.then(({ data }) => data)
.catch(er => er);
The problem in the above getActiveId definition is every time the catch of the getActiveId, the execution falls into the try block of the above endpoint definition. I wanted the execution should go into the catch block endpoint definition function. So that I could call next(err) to call the default express error handling middleware.
So I tried the following mockup code to mimic the same with promise reject.
exports.getActiveId = id => {
const __mockPromise = () => {
return new Promise((resolve, reject) => {
reject('Problem in getActiveId')
})
}
return new Promise((resolve, reject) => {
__mockPromise().then(({ data }) => resolve(data)).catch(er => { console.log('in catch....'); reject(er) })
});
}
I expected the above function will go into the catch block of the end point function definition.
But this time I am getting the following error,
in catch....
(node:32897) UnhandledPromiseRejectionWarning: Problem in getActiveId
(node:32897) 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(). (rejection id: 4)
How can I fix this error and bypass the execution to error middleware?
With your current code, api.getActiveId always returns a resolved promise
if the axiosInstance.get succeeds, it resolves to the data
if the axiosInstance.get fails, the .catch(er => er) makes it resolve to the er.
If you want api.getActiveId to return a promise that is rejected with er, omit the .catch(er => er).
For example, if you run Node.js with the following input
const getActiveId = () => Promise.reject("error")
.then(({ data }) => data);
async function test() {
try {
const result = await getActiveId();
console.log(result);
} catch (err) {
console.error(err);
}
}
test();
the console.error statement will be reached and no unhandled promise rejection will be reported.
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 3 years ago.
I have the following code writed in nodejs express and firebase
route.js
try{
const test = await invoiceData.setAssignsInvoiced();
res.json({
status: true,
message: "Successful Invoice Generation"
});
}catch (e) {
res.status(500).json({
status: false,
message: "Internal Server Error",
data: e
});
}
InvoicesStorage.js
setAssignsInvoiced = async() => {
return new Promise(async (resolve,reject)=>{
try {
await _getAssignsForInvoiced(this);
this.assignsForInvoiced.forEach(async assing => {
let aux = assing._key.path.segments.length;
let ref = assing._key.path.segments[aux - 1];
await _updateAssignsToInvoiced(assing.data(),ref);
});
resolve(true)
} catch (error) {
console.error(error)
reject(error)
}
})
};
const _updateAssignsToInvoiced = async (assing, ref) => {
try {
const { invoiceNum } = assing.data(); //Here's an intentional error
await db
.collection("leadAsign")
.doc(ref)
.update({
invoiced: true,
updateDate: Date.now() - 240 * 60 * 1000,
invoiceNum
});
} catch (error) {
console.error(error);
throw new Error("Error at update to invoiced assigns");
}
};
How I hope it works:
According to me, I should throw out a synchronous error because my code has "await" and stop the system.
The answer I have:
the code runs asynchronously, that is, after calling the function the "await" has no effect and answers a "res.json" with status 200 and it is only after it throws the next error.
TypeError: assing.data is not a function
at _updateAssignsToInvoiced (D:\$Workzone\gd_fridays_h\src\controllers\invoices\InvoicesStorage.js:90:35)
at D:\$Workzone\gd_fridays_h\src\controllers\invoices\InvoicesStorage.js:55:23
at Array.forEach (<anonymous>)
at D:\$Workzone\gd_fridays_h\src\controllers\invoices\InvoicesStorage.js:51:37
true
POST /generateSingle 200 5182.650 ms - 57
(node:5600) UnhandledPromiseRejectionWarning: Error: Error at update to invoiced assigns
at _updateAssignsToInvoiced (D:\$Workzone\gd_fridays_h\src\controllers\invoices\InvoicesStorage.js:102:11)
at D:\$Workzone\gd_fridays_h\src\controllers\invoices\InvoicesStorage.js:55:23
at Array.forEach (<anonymous>)
at D:\$Workzone\gd_fridays_h\src\controllers\invoices\InvoicesStorage.js:51:37
(node:5600) 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(). (rejection id: 4)
(node:5600) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
async/await doesn't work as you're expecting it to inside a forEach loop. An abundance of info on that specific issue here: https://stackoverflow.com/a/37576787/4043746
To fix your problem, you could use a for/of loop:
setAssignsInvoiced = async () => {
return new Promise(async (resolve, reject) => {
try {
await _getAssignsForInvoiced(this)
for (const assign of this.assignsForInvoiced) {
let aux = assign._key.path.segments.length
let ref = assign._key.path.segments[aux - 1]
await _updateAssignsToInvoiced(assign.data(), ref)
}
resolve(true)
} catch (error) {
console.error(error)
reject(error)
}
})
}
However, I'd also be tempted to suggest not returning a promise, as you're essentially doing that due to it being an async function. Something like this should work and is cleaner imo:
setAssignsInvoiced = async () => {
try {
await _getAssignsForInvoiced(this)
for (const assign of this.assignsForInvoiced) {
let aux = assign._key.path.segments.length
let ref = assign._key.path.segments[aux - 1]
await _updateAssignsToInvoiced(assign.data(), ref)
}
} catch (error) {
console.error(error)
// Re-throwing the error to pass the error down, just like you've
// done inside your _updateAssignsToInvoiced function's catch
throw new Error('Error setting assigns')
}
}
Async/await inside a forEach() loop will not wait until all the async operations inside the loop is completed.
One approach would be using Promise.all() like so:
const setAssignsInvoiced = async () => {
try {
await _getAssignsForInvoiced(this);
await _updateAssignsList(this.assignsForInvoiced);
return true;
} catch (error) {
console.error(error);
return new Error(error);
}
};
const _updateAssignsList = assignsList => {
return Promise.all(
assignsList.map(async assign => {
let aux = assign._key.path.segments.length;
let ref = assign._key.path.segments[aux - 1];
return await _updateAssignsToInvoiced(assign.data(), ref);
})
);
};
I've just extracted the async loop process to a separate function which return a Promise.
im trying to get the promise error, and throw to the "try catch" to concentrate the return of the error in one place.
like this:
async schedule(req, res) {
try {
//here is the function that returns a promise
service.search()
.then(async data => {
if (data.length > 0) {
res.status(200).json("OK!");
}
})
.catch(async error => {
//here i want to throw this error to the "try catch" to return the error message
throw new Error(error);
})
}
catch (error) {
res.status(400).json(error);
};
}
but when goes to "throw new Error(error);" gives me the message:
(node:15720) 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(). (rejection id: 2)
warning.js:27
someone could help me to understand what im doing wrong?
thank so much!
Rafael
UPDATE
based on the Marcos answer, i did:
async schedule(req, res) {
try {
const data = await service.search();
if (data.length > 0) {
res.status(200).json("OK!");
}
}
catch (error) {
res.status(400).json(error);
};
}
and worked... Now i understand how to handle this errors... thanks!
You either use async/await with a try/catch or .then/.catch, you don't mix both ways.
async schedule(req, res) {
try {
//here is the function that returns a promise
// If service.search rejects, it will go to the `catch`
const data = await service.search()
if (data.length > 0) {
return res.status(200).json("OK!");
}
// do something here
// res.status(400).send('Invalid data')
// throw new Error('Invalid data')
} catch (error) {
res.status(400).json(error);
}
}
or
schedule(req, res) {
service.search()
.then(data => {
if (data.length > 0) {
res.status(200).json("OK!");
}
})
.catch(error => {
res.status(400).json(error);
})
}
I'm creating an API using Node.js/TypeScript running Express. Below is an excerpt from my get method. An error is being triggered in the format method, which throws an error that is caught by the promise, but not propagated to the parent promise after a throw:
this.getModel(objectName).findAll(queryParameters).then(function(databaseObjects) {
for (let databaseObject of databaseObjects) {
var jsonObject = {};
//console.log("Database object: ");
//console.log(databaseObject);
transform.baseFormat(databaseObject, jsonObject)
.then(() => transform.format(databaseObject, jsonObject))
.then(() => {
res.locals.retval.addData(jsonObject);
}).catch((e) => {
console.log("Caught error during format of existing object: ");
console.log(e);
throw e;
});
}
})
.then(() => {
if (metadata) {
this.metadata(objectName, false, transform, res.locals.retval);
delete queryParameters.limit;
delete queryParameters.offset;
console.log("RUNNING METADATA COUNT: ");
this.getModel(objectName).count(queryParameters).then(function(count) {
res.locals.retval.setMetadata("records", count);
return next();
}).catch(function(e) {
this.error(e, res);
return next();
});
} else {
console.log("NO METADATA");
return next();
}
})
.catch((e) => {
// TODO: Move status into error() function
console.log("500 Error on GET");
console.error(e);
res.locals.retval.addError(ErrorCode.InternalError, e);
res.status(ErrorCode.InternalError).send(res.locals.retval);
return next();
});
Here's the output:
(node:8277) Warning: a promise was created in a handler at /Library/WebServer/adstudio/dist/server.js:555:51 but was not returned from it, see
at Function.Promise.bind (/Library/WebServer/adstudio/node_modules/bluebird/js/release/bind.js:65:20)
Caught error during format of existing object:
Test Error
END FUNCTION HAS BEEN REACHED!
Then the request fails to finish.
I've read a lot on Promises and I haven't been able to find an issue/solution similar to mine.
http://bluebirdjs.com/docs/warning-explanations.html
http://taoofcode.net/promise-anti-patterns/
https://www.reddit.com/r/javascript/comments/4bj6sm/am_i_wrong_to_be_annoyed_with_promise_error/
https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
Chained promises not passing on rejection
http://wiki.commonjs.org/wiki/Promises/A
https://promisesaplus.com/
Running inside that for-loop is not asynchronous, so your promise is resolving basically as soon as the loop finishes, yet before all your formatting finishes.
Use a promise control flow, like bluebird's Promise.each which is serial or just Promise.all. Then any exceptions will be caught.
this.getModel(objectName).findAll(queryParameters).then(function (databaseObjects) {
var promises = databaseObjects.map(databaseObject => {
var jsonObject = {}
// console.log("Database object: ");
// console.log(databaseObject);
return transform.baseFormat(databaseObject, jsonObject)
.then(() => transform.format(databaseObject, jsonObject))
.then(() => {
res.locals.retval.addData(jsonObject)
}).catch((e) => {
console.log('Caught error during format of existing object: ')
console.log(e)
throw e
})
})
return Promise.all(promises)
})
.catch((e) => {
// TODO: Move status into error() function
console.log('500 Error on GET')
console.error(e)
res.locals.retval.addError(ErrorCode.InternalError, e)
res.status(ErrorCode.InternalError).send(res.locals.retval)
return next()
})