I want to unit test a function.
In that function I'm using Co with a generator function.
When an error occurs I catch it, and I call cb with the error
In my unit test I make a false assertion but mocha doesn't report it, it just times out:
//function to test
function start(data, cb) {
co(function * coStart() {
yield Promise.reject('err'); // yield error for sake of demo
}).then(function(result){
return cb(null, result);
}).catch(function (err) {
// we get here
return cb(err);
});
}
// mocha test
it('fails on incorrect data', function(done){
MyModel.start({'foo': 'bar'}, function(err, res){
assert.equal(err, 'err2'); //this fails but mocha just stops here and times out
done();
});
});
Clearly I'm doing something wrong here?
I know you can return a promise to mocha and omit the done-callback in the test, but my function 'start' cannot return a promise, its like middleware so it should work with a callback
Your code does something similar to this:
Promise.reject('err')
.catch(() => {
// throw another exception
throw 'foo';
});
That is: within the .catch() clause, another exception is thrown (in your case, the exception thrown by assert.equal(err, 'err2')), synchronously, which isn't handled (by, for instance, another .catch() clause). This will cause the second exception to be ignored (see this answer for an explanation), but it will stop the test case from finishing (the done() line is never reached, hence timing out the test case).
If you really need callback support, you can work around this by either adding another .catch() clause in start(), or by calling the callbacks asynchronously:
return setImmediate(function() { cb(null, result) });
...
return setImmediate(function() { cb(err) });
...
But ideally, you should consider the possibility of removing callback support entirely and just passing around the promises. Mocha supports promises out of the box, so the code would look something like this:
function start(data) {
return co(function * coStart() {
yield Promise.reject('err');
});
}
it('fails on incorrect data', function(){
return start({'foo': 'bar'}).then(function() {
throw new Error('should not be reached');
}, function(err) {
assert.equal(err, 'err2');
});
});
Related
I am working in a new codebase that has a pattern where some reusable functions return a promise from a chain, but they don't handle errors.
Here is an example:
function createScheduleForGroup(group) {
if (notInProduction()) {
group.gschedScheduleId = '0';
return Bluebird.resolve(group);
}
// note: schedule is a global within the file
return schedule.createSchedule(group.name).then(function (schedule) {
group.gschedScheduleId = schedule.id;
return group.save();
});
}
In the example above, there isn't a .catch or a reject function passed to the .then.
The function is eventually used in an express route where the error is handled:
router.post('/schedule', function(req, res, next) {
scheduleLogical
.createScheduleGroup(req[config.entity])
.then(function(group) {
res.status(201).json(group);
})
.catch(next);
// if creteScheduleGroup throws an error it is handled here
Is it a common pattern to not define error handlers for a promise returned from a function, and anticipate whoever uses the function to attach the appropriate error handlers?
To make this clearer for my own understanding, I made a simulation of this specific function and all the functions used within the promise chain. Here it is:
function getScheduleMock() {
// this promise promisifys an older api that uses callbacks
return new Promise((resolve, reject) => {
// note this is simulating an api call:
const response = Math.round(Math.random());
// 0 === err, 1 === success
if(response === 0) return reject(response);
resolve(response);
})
// error hanlding :)
.catch(err => {
console.log(err);
return Promise.reject(err);
// there is an error handling function that logs the errors. If the error doesn't match expected errors, it rejects the error again as I have here.
})
.then(responseData => {
return Promise.resolve(responseData);
})
}
function createScheduleForGroupMock() {
return getScheduleMock().then(responseData => Promise.resolve('everything worked :)'));
// Note: group.save() from the original returns a promise
// just like the actual example, the promise is returned
// but there isn't any form of error handling except within the getScheduleMock function
}
createScheduleForGroupMock(); // when the error is rejected in the .catch() in getScheduleMock, the error is unhandled.
/* ***************** */
/* the createScheduleForGroup method is used within an express route which has error handling, here is an example of the code: */
// router.post('/schedule', function(req, res, next) {
// scheduleLogical
// .createScheduleGroup(req[config.entity])
// .then(function(group) {
// res.status(201).json(group);
// })
// .catch(next);
// if creteScheduleGroup throws an error it is handled here
I'm fairly new to error handling promises and from what I've been reading and practicing, I generally felt you should always include an error handler. The codebase I'm working in has a lot of methods that use the pattern of createScheduleForGroup where there isn't an error handler defined in the function, but instead, it is handled and attached after the function is used.
Some of the functions used within createScheduleForGroup handle their own errors, and I'm confused and curious about the balance on when to handle errors in a function that returns a promise and when to attach them when you use the function.
Is it a common pattern to not define error handlers for a promise returned from a function, and expect whoever uses the function to attach the appropriate error handlers?
Yes, totally. It's not just "a common pattern", it's the absolute standard pattern.
Just like you don't put a try/catch statement in every synchronous function, you don't put a .catch callback on every promise that you return. In fact, it's considered an antipattern to catch errors that you cannot handle.
You can have an error handler in each function.
function aPromise() {
return new Promise(function(resolver, reject){
//Handle any error here and attach different information via your own errror class
})
}
async function parentProcess() {
try{
await aPromise()
}
catch(e) {
//Handle and attach more info here
}
}
function grandParentProcess() {
try{
parentProcess();
}
catch(e){
//Handle the error
}
}
You don't essentially have to handle error in the parent function if the grand parent function handles it to avoid the UnhandledPromiseRejection error.
I just realized all of my test code has a race condition.
My style pattern follows something like this:
const myFunc = (callback) => {
return somePromise().then((result) => {
return someOtherPromise();
}).then((result) => {
db.end(() => {
callback();
});
}).catch((err) => {
db.end(() => {
callback(err);
});
});
};
I'm testing with Jest. Test code looks something like this.
it('should work', (done) => {
// mock stuff
let callback = () => {
expect(...);
done();
};
myFunc(callback);
});
I have dozens of functions and tests following this pattern. The last test I wrote was giving me a Jest matcher error in my callback. After much confusion, I realized that the first callback execution is throwing the Jest failure, and the callback with the err parameter is being executed and failing before done() is called by the first callback execution.
I'm realizing this pattern might be absolutely terrible. I've managed to beat the race condition by having certain expect() calls in certain order, but that's no way to do this.
How can I remove the potential for the race condition here?
I'm open to completely changing the style I do this. I know my Javascript isn't particularly amazing, and the system is still pretty early in its development.
My colleague advised me that this is a good case to use async/await.
See a new version of the code under test:
const myFunc = async (callback) => {
let other_result;
try {
let result = await somePromise();
other_result = await someOtherPromise(result);
} catch (err) {
db.end(() => {
callback(err);
});
return;
}
db.end(() => {
callback(null, other_result);
});
};
I updated things a bit to make it seem more real-world-ish.
I know this makes myFunc return a promise, but that's okay for my use-case. By doing this, I'm ensuring that the callback is only executed once, preventing the Jest error from getting caught up elsewhere.
EDIT:
I'm realizing that this is the same as if I had moved the catch block to be before the final then block, I would have the same behavior :/
suppose:
function f() {
return p1()
.then(function(p1res) {
console.log('p1 ok');
return Promise.resolve(p1res);
}, function(err) {
console.log('p1 err '+err);
return Promise.reject(err);
}).then( ... proceed
are the statements
return Promise.resolve(p1res);
and
return Promise.reject(err);
needed?
Are the statements return Promise.resolve(p1res); and return Promise.reject(err); needed?
Yes, this or something equivalent is needed if you plan to chain additional then handlers from those handlers and therefore need to preserve ("pass through") the value and status of the promise. However, even if are you going to do that:
Instead of return Promise.resolve(p1res); it would be simpler and equivalent to just say return p1res;.
Instead of return Promise.reject(err); it would be simpler and equivalent to just say throw err;, to continue the promise on the error path with the "reason" err.
However, if your goal is merely to have a handler to log the status, you do not need to, and probably do not want to, chain subsequent handlers from there, since that will require you to go to extra trouble to ensure that the status reporting handlers return the value or re-throw the error for the benefit of the downstream handlers. Instead, you can put the status reporting handler on a separate "branch", and not worry about what they return or pass through:
function f() {
let result = p1();
result . then(
function(p1res) { console.log('p1 ok'); },
function(err) { console.log('p1 err ', err); });
result . then(
...proceed
I'm using promise library Bluebird in all my Node.js projects. For getting the content of the first existent file from a list of file paths I use Promise.any successfully as follows:
Promise.any([
'./foo/file1.yaml',
'./foo/file2.yaml',
'./foo/file3.yaml'
], function(filePath) {
_readFile(filePath);
}),then(function(fileContent) {
_console.log(fileContent);
});
My question is, how can i leave the Promis.any loop early if i get an error which is different from "file not found", when reading a file? The following code illustrates my question:
Promise.any([
'./foo/file1.yaml',
'./foo/file2.yaml',
'./foo/file3.yaml'
], function(filePath) {
_readFile(filePath)
.catch(function(err) {
var res = err;
if (err.code == FILE_NOT_FOUND) {
// continue the Promise.any loop
} else {
// leave the Promise.any loop with this error
err = new Promise.EarlyBreak(err);
}
Promise.reject(err);
});
}).then(function(fileContent) {
_console.log(fileContent);
}, function(err) {
// the first error different from FILE_NOT_FOUND
});
May be Promise.any is not the right function?
Leaving a Promise.any() loop early is conceptually problematic in that Promise.any() is an aggregator not a loop, and accepts an array of promises, each of which has a life of its own, not determined by Promise.any().
However, starting with an array of paths, the loop you seek can be expressed as a paths.reduce(...) expression, which builds a .catch() chain, straightforwardly as follows :
function getFirstGoodFileContent(paths) {
paths.reduce(function(promise, path) {
return promise.catch(function() {
return _readFile(path);
});
}, Promise.reject()); // seed the chain with a rejected promise.
}
Catch chain: credit Bergi
The .catch chain thus built, will progress to the next iteration on failure, or skip to the end of the chain on success. This flow control is the inverse of what happens in a more normal .then chain (seeded with a fulfilled promise).
But that's not quite everything. An extra condition is required - namely to "leave the [Promise.any] loop early if I get an error which is different from 'file not found'". This is very simply engineered into the catch chain by sending all errors except FILE_NOT_FOUND down the success path, thereby :
effecting the required flow control (skipping the rest of the chain), but
ending up with an error condition going down the success route - undesirable but recoverable.
function getFirstGoodFileContent(paths) {
paths.reduce(function(promise, path) {
return promise.catch(function() {
return _readFile(path).catch(function(err) {
if (err.code == FILE_NOT_FOUND) {
throw err; // Rethrow the error to continue down the catch chain, seeking a good path.
} else {
return { isError: true, message: err.code }; // Skip the rest of the catch chain by returning a "surrogate success object".
}
});
});
}, Promise.reject()).then(function(fileContent) {
// You will arrive here either because :
// * a good path was found, or
// * a non-FILE_NOT_FOUND error was encountered.
// The error condition is detectable by testing `fileContent.isError`
if (fileContent.isError) {
throw new Error(fileContent.message); // convert surrogate success to failure.
} else {
return fileContent; // Yay, genuine success.
}
});
}
So you can now call :
getFirstGoodFileContent([
'./foo/file1.yaml',
'./foo/file2.yaml',
'./foo/file3.yaml'
]).then(function(fileContent) {
_console.log(fileContent);
}, function(error) {
// error will be due to :
// * a non-FILE_NOT_FOUND error having occurred, or
// * the final path having resulted in an error.
console.log(error);
});
Quick question, if I do this:
setInterval(function() {
try {
riskyFunc();
} catch(e){
console.log(e);
}
}, 1000);
In my head I am thinking that if anything goes wrong in riskyFunc(), it will be caught. Is this true? There are also some async calls I know of for sure inside riskyFunc().
Yes, it will be caught: but only when the callback is executed. That is, if riskyFunc throws an exception, it won't be caught in your example until the callback executes in one second.
You've probably heard before that you have to be careful with exceptions when using asynchronous methods, and the usual mistake people make is this:
try {
setInterval(function() {
riskyFunc();
}, 1000);
} catch(e) {
console.error(e);
}
They are confused when riskyFunc throws an exception and it isn't caught. It isn't caught because the exception doesn't happen when you call setInterval; it happens when setInterval invokes the anonymous function sometime in the future, which is outside of the context of the original try/catch block. You are doing it the correct way: by doing the exception handling inside the callback.
If riskyFunc in turn invokes asynchronous calls, those too have to handle exceptions in this manner. For example:
function riskyFunc() {
// do some stuff
asyncFn(function(){
throw new Error('argh');
}
}
That exception will not get caught in the try/catch block inside your setInterval call. You'll have to keep applying the pattern on down:
function riskyFunc() {
// do some stuff
asyncFn(function() {
try {
// work that may throw exception
} catch(e) {
console.error(e);
}
}
}
If you want the exception to "propagate up", you'll have to use promises, or some other way to indicate success/failure. Here's a common method, by using a "done" callback that is capable of reporting an error:
function riskyFunc(done) {
// do some stuff
asyncFn(function() {
try {
// do some more risky work
done(null, 'all done!');
} catch(e) {
done(e);
}
}
}
Then you can call that in your setTimeout and take into account possible asynchronous failures:
setTimeout(function() {
try {
riskyFunc(function(err, msg) {
// this will cover any asynchronous errors generated by
// riskyFunc
if(err) return console.error(err);
console.log(msg);
});
} catch(e) {
// riskyFunc threw an exception (not something it
// invoked asynchronously)
console.error(e);
}
}
setInterval already puts the block in an asynchronous block. And exceptions can't be caught in out-of-sync. The right pattern is to use a Promise object, and call a fail() operation if something goes wrong. For this example, I'm using jQuery's Deferred object, but any promise library has similar usage:
var promise = $.Deferred();
setInterval(function () {
try{
riskyFunc();
} catch (e) {
promise.reject(e);
}
promise.resolve(/* some data */);
}, 1000);
promise
.done(function (data) { /* Handled resolve data */ })
.fail(function (error) { /* Handle error */ });
Note that since you're using setInterval instead of setTimeout that this will be called every second unless you clear the timeout, so if you need to call the function multiple times in parallel, you might want an array of promises.
If riskyFunc is
function() {
process.nextTick(function() {
throw "mistake";
});
}
your catch block will not catch. I believe this is the case you are worried about, and all you can do is set global exception handlers, or hope for the best. (No, promises will not catch this. They are not magic.)
Thanks #Ethan Brown for the detailed explanation. I think your last setTimeout is missing the timer - see below
setTimeout(function() {
try {
riskyFunc(function(err, msg) {
// this will cover any asynchronous errors generated by
// riskyFunc
if(err) return console.error(err);
console.log(msg);
});
} catch(e) {
// riskyFunc threw an exception (not something it
// invoked asynchronously)
console.error(e);
}
}, 1000)