-
Notifications
You must be signed in to change notification settings - Fork 30.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
lib: consistent callback checking across project #3539
lib: consistent callback checking across project #3539
Conversation
@@ -90,7 +90,7 @@ exports.OutgoingMessage = OutgoingMessage; | |||
|
|||
OutgoingMessage.prototype.setTimeout = function(msecs, callback) { | |||
|
|||
if (callback) { | |||
if (typeof callback === 'function') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had originally done this with a regex... and I am realizing now why you might want to check for callback... what if it doesn't exist :s
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
although if (typeof callback === 'function')
will still evaluate false whether or not callback is defined.
Seem like the extra if statement here isn't neccessary
Likely |
LGTM. Just out of curiosity, what can break from this change that wouldn't already be a crash before? |
@@ -482,7 +484,7 @@ OutgoingMessage.prototype.write = function(chunk, encoding, callback) { | |||
|
|||
function writeAfterEndNT(self, err, callback) { | |||
self.emit('error', err); | |||
if (callback) callback(err); | |||
if (typeof callback === 'function') callback(err); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an interesting one. Prior to the change a callback that isn't a function would throw. In this instance it would fail silently
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably needs an else if (callback) throw …
or something like that
@cjihrig Can't tell without checking more closely, but it seems extremely likely that things will fail differently. So, someone may have code wrapped in a try/catch that expects a Which actually brings up a point: Should these be changed so that there's an EDIT: I guess one advantage is you could be more stringent and say that |
That is REALLY pushing the line between a fix and a breaking change, especially since most of these are asynchronous. I wouldn't be opposed to throwing when a truthy, non-function value is passed, but then we're definitely into breaking change territory. |
@Trott just brought up the exact thing I was just thinking. With this change it is possible for things to fail silently. That being said, is it the duty of node.js to do type checking on function calls? |
@cjihrig I'm OK with removing the |
I'm still not really convinced that this is semver-major, but I'm fine with making it semver-major just for the sake of extra caution. |
Question for ya'll... how do we avoid the two patterns from diverging in the future. Would it be unreasonable to make a linting rule for this type of thing? |
CI, in case there are any surprises: https://ci.nodejs.org/job/node-test-pull-request/623/ |
this.on('timeout', callback); | ||
} | ||
else { | ||
throw new TypeError('callback must be a function'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not necessary. The on
function takes care of it already. I would say you can simply remove the if
guard around on
, once
, and removeListener
, as they check if the parameter passed to them are functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
didn't mean to bury this but realized that there was a bug here.
Want to continue the conversation at --> /~https://github.com/nodejs/node/pull/3539/files#r43200588
I think semver-major is the safest bet on this one. This is exactly the kind of change that leads to "oh crap we broke the world" moments immediately after cutting a new release. Throwing on a truthy non-function is a very good idea, I think. A linting rule on callback type checking would be an interesting approach. We really ought to be more consistent about it. |
Throughout the codebase there are two pattern for checking if a callback has been provided as an argument. This PR replaces all instances to do explicit type checking. Closes #3536
this.on('timeout', callback); | ||
} | ||
else if (callback) { | ||
throw new TypeError('callback must be a function'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@thefourtheye so you are suggesting that this Type check shouldn't be done.
@Trott suggested otherwise in --> #3539 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jasnell was for it as well --> #3539 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm for it if there is the typeof
check. I think @thefourtheye is saying that you can discard the typeof
check and the else
block and just pass blindly to .on()
because .on()
will throw a TypeError
if the callback is not a function. At least, that's my understanding. So I'm for either approach, really. Don't make no neither nor to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In other words, I think @thefourtheye is saying this would be fine:
if (callback) {
this.on('timeout', callback);
}
Which throws a wrench in the whole "standardize on typeof
checks" approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need that if
guard if and only if callback
might be optional and therefore undefined
. I didn't look at the code to see if that is actually possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right... At least the event handler code doesn't need this if block as it is done anyway in on
, once
, {add,remove}listener
functions
Any thoughts on if this is a change we want to adopt? |
seems like maybe we need a I'm down with this either way, the current truthy is a smell and is now inconsistent across lib/ |
I like the idea of a generic util used across the lib for consistency. Would it make sense for this to land first as an intermediary step to make sure too many things don't blow up, and then add the refactoring with the util afterwards? |
Well, landing this first means that we go from I think it would be best to add a truthy-non-function check with |
I definitely agree with @rvagg
|
Ok I did a first pass at implementing the util function and came across some weirdness while running the test suite. Turns out the expected behavior of some functions that take callbacks is to fail silently if a bad argument is given :( socket.close is such an example What do people think of being stricter in these instances... obviously semver-major, but is it desirable to change this much underlying logic? |
So digging further into dgram Socket.close it would appear that the callback argument is optional. It is not documented that an incorrect argument will be ignored. I have not gone through all instances of an optional callback, but at the very least we can deduce that the narrative is inconsistent. This will definitely be breaking, but I personally see value in consistency. |
anyone have thoughts on the above information? |
Looks like all other |
I am closing this for now as it is not entirely obvious that this will be easy to land (with various ways to handle callbacks) |
Throughout the codebase there are two patterns for checking if a
callback has been provided as an argument. This PR replaces all
instances to do explicit type checking.
Closes #3536