I'm getting a little confused with Promises in JavaScript, when currently trying to add some validation and error message handling to a function.
This code works:
hf.tokenize().then(function(payload) {
// ...
}).then(function(payload) {
// ...
}).catch(function(err) {
console.log(err);
});
But does no real error handling, just dumps them in the console. When I try and do this:
hf.tokenize(function(err, payload) {
if (err) {
// Handle error
}
return;
}).then(function(payload) {
// ...
}).then(function(payload) {
// ...
}).catch(function(err) {
console.log(err);
});
I get the following error:
Uncaught TypeError: Cannot read property 'then' of undefined
at HTMLInputElement anonymous
I've looked at a few other questions that give solutions to similar error responses, but the difficulty comes in that I'm not even sure what I'm meant to be doing at this point, or what to return.
Javascript promises are a new syntax but js has had async before the syntax in the form of callbacks. Usually a method has overloads. If you call it without parameters then you can use the Promise syntax and if you call it with parameters then you use the old callback style (where one the the parameters is a function(){ do stuff }).
https://braintree.github.io/braintree-web/current/HostedFields.html#tokenize
callback May be used as the only parameter of the
function if no options are passed in. The second argument, data, is a
tokenizePayload. If no callback is provided, tokenize returns a
function that resolves with a tokenizePayload.
You are mixing them and since you are calling the overload with parameters it doesn't have a return value (the void in -> {Promise|void} hence the "undefined" error.
Related
I have defined two simple functions in nodejs:
function findEntityDetailsAsIs(modelname, callback) {
modelname.find({}, function(error, result) {
if (error)
callback (error, null);
else {
//console.log(result);
callback (null, result);
}
});
};
This is my first function and another function is
function printEntitydetails(error, entitydetails, callback) {
console.log(entitydetails);
}
I am trying to call these functions as such
findEntityDetailsAsIs(fieldLabel, printEntitydetails(error, entitydetails));
But as i try to run this function call it throws an error
ReferenceError: error is not defined
But error is just as placeholder object i am passing from callback.
findEntityDetailsAsIs(fieldLabel, printEntitydetails(entitydetails));
I tried skipping error in the call but this time it gives this error.
ReferenceError: entitydetails not is not defined
As per my knowledge the findEntityDetailsAsIs should provide the context for the entitydetails, as i have provided a callback(null, result).
Your function findEntityDetailsAsIs expects to get the callback function, not the execution result of it.
You need to provide only the function name:
findEntityDetailsAsIs(fieldLabel, printEntitydetails);
When you run it like you do, you pass to findEntityDetailsAsIs the result of printEntitydetails instead of the function itself. since the function returns nothing, you get undefined
While trying to move my code to promises after working only with callbacks,
(without breaking the function's interface) I've encountered an issue.
in a code example like this:
function callMeWithCallback(args, next) {
Promise.resolve()
.then(()=> {
return next("some error will be thrown here");
}).catch((error)=> {
return next("Error in callMeWithCallback func");
});
}
callMeWithCallback(args, function(){
throw "some error";
})
What happens is that after resolving the promise in callMeWithCallback func and calling the callback once, an error is thrown, and the code comes back the catch in the callMeWithCallback function and calls again the callback.
I would like the function callMeWithCallback to call the call back only once (wether i case of an error or not) what changes do I need to imply?
I warmly recommend that you use a library like bluebird that normalizes this for you - your code would be:
function callMeWithCallback(args, next) {
// in real code always reject with `Error` objects
var p = Promise.reject("some error will be thrown here");
p.asCallback(next); // converts a promise API to a callback one, `throw`s.
return p;
}
This guarantees "next" will be called once at most.
If you do not want bluebird you can implement this yourself:-
function asCallback(promise, callback) {
promise.then(v => callback(null, v), e => callback(e));
}
Which would be fine as long as you keep the calls in one place. The important part is to not dispatch the calls to "next" yourself.
I'm trying to request the data from server using dojo jsonrest store. While requesting i'm catching the callback to do some stuff. For example
this.myStore.get(paramValue).then(lang.hitch(this, function (res) {
//do something like this.globalVal = res;
}, function (err) {
console.log(err);
//throw error
}));
But the above code only works when the request returns success, i.e. it dose enter in the first block of deferred on success return, but when some error occurred, it failed to reach in the error callback and hence i'm not able to catch the errors returned by server.
If i do the above code without using lang.hitch like this
this.myStore.get(paramValue).then(function (res) {
//do something like this.globalVal = res;
}, function (err) {
console.log(err);
//throw error
});
Then it works. i.e. it will now reach to the error callback as well and i can throw the appropriate error to user.
So why dose this happen, and if lang.hitch is not something that can be used with deferred then what to use?
Thanks
Hitch accepts two arguments, context and the function which is to be executed in the preceding context. At the moment you're using three, that won't work. You're trying to wrap two functions into the same hitch. You'll need to wrap them each in a separate hitch:
this.myStore.get(paramValue).then(
lang.hitch(this, function success (res) {
console.log('Success');
}),
lang.hitch(this, function error (err) {
console.log('Error');
})
);
Mocha website states:
"To make things even easier, the done() callback accepts an error, so we may use this directly: [see their example]"
So lets try that:
it('works',function(done){
expect(1).to.be(1)
done( new Error('expected error') )
})
/* Insert the error manually for testing and clarity. */
run it and:
1 failing
1) works:
Error: expected error
at Context.<anonymous>
[stack trace]
How do we make the test pass when the error response is the desired result?
That's not async. The callback function is just there for you to inform mocha that you're testing async code and so mocha shouldn't run the next test until you call the callback function. This is an example of how to use the callback function:
it('works',function(done){
setTimeout(function(){
// happens 0.5 seconds later:
expect(1).to.be(1);
done(); // this tells mocha to run the next test
},500);
});
Also, mocha does not handle any form of exceptions, async or otherwise. It leaves that up to an exception library of your choosing. In your case you're using expect.js? If so, expect handles expected errors via the throwException method (also called throwError):
it('throws an error',function(done){
expect(function(){
throw new Error('expected error');
}).to.throwError(/expected error/);
});
Now, in general async code in node.js don't throw errors. They pass errors to the callback as parameters instead. So to handle async errors you can simply check the err object:
// using readFile as an example of async code to be tested:
it('returns an error',function(done){
fs.readFile(filename, function (err, data) {
expect(err).to.be.an(Error);
done(); // tell mocha to run next test
})
});
So use to.throwError() if you're checking synchronous errors and to.be.an(Error) if you're checking async errors.
Additional notes:
The first time I saw this I was stumped. How can mocha know that the test is synchronous or asynchronous when the only difference is weather the function you pass to it accepts an argument or not? In case you're like me and are scratching your head wondering how, I learned that all functions in javascript have a length property that describes how many arguments it accepts in its declaration. No, not the arguments.length thing, the function's own length. For example:
function howManyArguments (fn) {
console.log(fn.length);
}
function a () {}
function b (x) {}
function c (x,y,z) {}
howManyArguments(a); // logs 0
howManyArguments(b); // logs 1
howManyArguments(c); // logs 3
howManyArguments(howManyArguments); // logs 1
howManyArguments(function(x,y){}); // logs 2
So mocha basically checks the function's length to determine weather to treat it as a synchronous function or asynchronous function and pauses execution waiting for the done() callback if it's asynchronous.
Even more additional notes:
Mocha, like most other js unit test runners and libraries, work by catching errors. So it expects functions like expect(foo).to.be.an.integer() to throw an error if the assertion fails. That's how mocha communicates with assertion libraries like expect or chai.
Now, as I mentioned above, a common idiom in node is that async functions don't throw errors but passes an error object as the first argument. When this happens mocha cannot detect the error and so can't detect a failing test. The work-around to this is that if you pass the error object from the async code to the callback function it will treat it the same as a thrown error.
So, taking one of my examples above:
it('executes without errors',function(done){
fs.readFile(filename, function (err, data) {
done(err); // if err is undefined or null mocha will treat
// it as a pass but if err is an error object
// mocha treats it as a fail.
})
});
Or simply:
it('executes without errors',function(done){
fs.readFile(filename,done);
});
Strictly speaking, this feature is a bit redundant when used with libraries like expect.js which allows you to manually check the returned error object but it's useful for when your assertion library can't check the error object (or when you don't really care about the result of the async function but just want to know that no errors are thrown).
You can also return your async such as promise as below.
it('Test DNA', () => {
return resolvedPromise.then( (result)=>{
expect(result).to.equal('He is not a your father.');
},(err)=>{
console.log(err);
});
});
So the general convention for callback functions in Node.js is to "reserve" the first parameter for an error (if one exists). For example:
callSomeBlockingFcn( function callbackWhenDone(err, result) {
if( err ) ...
});
If you need to return more than one error--say multiple data validation errors, for example--is it considered poor form to pass an array of error objects? Example:
var callSomeBlockingFcn = function(callback) {
// multiple errors to report back...
callback( [ err1, err2, ...] );
}
Or is it preferable to avoid arrays and return a single object with a property referencing an array (if necessary)? Example:
var callSomeBlockingFcn = function(callback) {
// multiple errors to report back...
callback( { errors: [ err1, err2, ...] } );
}
3 years later:
Anyone that puts an array in a callback will make me mad.
The correct solution is to return an error as the first argument. If you want to return multiple errors you are probably using errors for non-exceptional cases.
In which case it should go in the "value" slot of the callback, i.e. the second argument. The first argument is for a single, unexpected operational error.
If you have multiple unexpected operational errors (unlikely) you can do something like this MultiError
Original:
I think there's nothing wrong with returning an array of errors.
Although you could return a new custom ValidationError which has a property "messages" which is an array.
a)
function validateX(x, cb) {
...
if (errorMessages) {
return cb(errorMessages);
}
}
b)
function ValidationError(msgs) {
this.messages = msgs;
}
function validateX(x, cb) {
...
if (errorMessages) {
return cb(new ValidationError(errorMessages));
}
}
Found this question via a search for the same issue. Though I looked around and came to the conclusion that I do not believe that err should be anything but an Error or null.
The best "authoritative" source I've found is Nodejitsu's help topic:
http://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions
In node.js, it is considered standard practice to handle errors in asynchronous functions by returning them as the first argument to the current function's callback. If there is an error, the first parameter is passed an Error object with all the details. Otherwise, the first parameter is null.
But I think that you can sort of make an argument from intuition as to why it should be so. Although there are a lot of if (err) tests in code to decide if something was or wasn't an error, you shouldn't pass 0 or false or undefined or NaN or an empty string. You should be able to test with if (err == null) if you wished.
Passing back something in the err field that is non-null but doesn't match if (err instanceof Error) seems dodgy. So I'd suggest not using arrays or objects. If you did, note also that none of the errors in your array will identify the location where the aggregate error was created. That's the point where the "real error" occurred, because it was the moment of decision that the errors it was given were not something it could handle.
However, this means you'd need a bit more work to get that:
function MultipleError (errs) {
// http://stackoverflow.com/a/13294728/211160
if (!(this instanceof MultipleError)) {
return new MultipleError(errs);
}
Error.call(this);
this.errs = errs;
// captureStackTrace is V8-only (so Node, Chrome)
// https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
Error.captureStackTrace(this, MultipleError);
};
MultipleError.prototype.__proto__ = Error.prototype;
MultipleError.prototype.name = 'MultipleError';
MultipleError.prototype.toString = function () {
return 'MultipleError: [\n\t' + this.errs.join(',\n\t') + '\n]';
}
A bit overkill, perhaps. But if you really can't pick an error to represent the aggregation, and think someone might be interested in the set of errors instead of just one, it would seem (?) that's what you'd want to do...allows the caller to examine the errs array if they want.