1) Json doesn’t have undefined, also when you interact with other sources (db, cache,..)
2) null and undefined are totally different things, used in different cases.
When the value is null, you know it was defined
When the value is undefined, you know it wasn’t defined
Example, a validator should know if a variable is missing (undefined), or it’s presented without value (null)
Yes it does, when you skip a value, tada! That's undefined 😉
Because JS folks decided to do so. Other languages that only have a single nullish value use that for any scenario that requires nullish values. From JS we can also just use one, and what I say in this article is that you can just use undefined.
As I replied to other guy. The response should be consistent, if you skip this value, tada! an error should be thrown. Because you won’t know if the value is missing (eg: it has, but api fails to give it) or the value is not defined.
Other languages which don’t have it because they are more strict, where it’s bad when you try to use an undefined variable. JS was born as a mess, but growing up as a flexible language, that’s why we have undefined and null to use together
If the API fails, you should handle that in other ways (response headers and proper error handling), if the API responded with the wrong body, that will be a problem either if you use null or undefined, so I still don't see much value for null in this scenario. If a value can either come or not, then you can define it as optional (meaning Type | undefined), and in the front end you can just do value ?? "default value" or if (value === undefined) { throw // ... } or whatever 😄
You should take a look at optional chaining and nullish coalescing, saying "it's bad when you try to use an undefined variable" feels like you might not be aware of this operators, but long story short, it's no longer bad. You can even "call" undefined 🎉
With tech like TS, contracts are actually pretty easy to define. There are tools out there that turn all kind of type definitions in .d.ts files, that you can use from TS or JS with editors such as VSCode to get the type checking. If an API defines something as optional, in TS that's Type | undefined, so you can omit that key or send that Type and it will work :D ... if the types change in any way, you should update your type definitions accordingly (using the same tools), and you'll get errors directly in your editor 🎉
That’s the thing.
Eg from a json response, a “post” which has “deleted_at” is null means it’s not deleted. But if the “deleted_at” is missing, it should be considered as invalid api response
That depends on the API implementation. This is like saying:
You need to use null because I wrote if (value === null)
Which is kinda like a Straw Man. If the API has a poor implementation that requires null, that doesn't mean you need to use null in your entire codebase, that just means you need to use it in your surface of contact with said API.
Me neither, that doesn't have anything to do with null. You can just omit something when is "nullish" which translates to .... you guest it, undefined, and it has the added value that payloads are smaller 🎉
Absence and null are different things, as said.
What constraints are you under that these minor payload sizes matter? you’ll probably get more out of gzip than omitting nullish.
Payloads are smaller by omitting some characters, good.
But if it causes confusion between null (value is set as no value) and undefined (value is not set), I wouldn’t take it as a good thing.
"Absence and null are different things" ... how? The size of the payload doesn't only affect the request, also the parsing. You're literally loading something trough the wire and in memory that you could omit, just to use null X_X
Yup, it kinda sucks, but is preferable to return undefined for something that isn't defined ... you can still check if a variable exists before trying to access it, by using typeof:
But that's the thing, they aren't the same thing. One is trying to access something not declared in the scope, while the other is trying to access a property of something that is declared in the scope. You can test both with typeof === "undefined", but if you try to access something undeclared you get an error, which sucks, but that's how it works. Nowadays is very difficult to end up in this scenario tho, because editors will let you know if you're trying to access an undeclared value.
That would make the argument saying "there is no way of differentiate between intentionally set and unintentionally set" for undefined invalid, because with in you can figure out if it was intentionally set as undefined, or is actually missing from the object.
Because if you set it to null, in will return true as well, so that doesn't make it better than undefined, nor different. So another argument in favor of "they are the same".
Because there are other ways to check if something exists than in.
TL;DR: Because is an argument in favor of the article, I guess I could even update it with that :D
You should explain that to me. I mean you're the ones saying "null means something is defined but intentionally empty", so you're the ones saying there's a need for that kind of thing ... I'm just saying that using in you can achieve the same thing with undefined ... in both cases we are saying something is "defined but not quite", just with different wording ... and I still don't get why you need that distinction 🤣
and I still don't get why you need that distinction
Well, I tried to find an example but cannot :)
And in this whole long holywar I cannot see a single example why null is better. JSON is one example, but let's imagine we delete all null keys from objects after getting data. Okay now we have libraries which are requiring null, and we have to convert undefined to null when using them. And that's all.
Maybe null make life easier when you are not using TypeScript, I don't know. With TypeScript you can see type in the code, and without it people are probably doing console.log(data) all the time to see data shape, and if values are omitted this is confusing.
Good article, thanks for writing, please keep doing it!
Well not only me, also other people here were trying to explain to you, but seems you just didn’t want to get it.
In my experience, consistency is one of the most important thing. Example: my service exchanges data with other services, they will have the same schema where they agree how an object looks like.
When other service sends me a kebab request with “ketchup” is null, I understand this kebab should be made without ketchup.
If the “ketchup” key is undefined, I understand that this kebab is on hold, because its definition is incomplete.
But if the “ketchup” key is missing? Wtf? Maybe the request was broken in the middle? Maybe the other service updated new schema and changed the key? Maybe the “ketchup” key is deprecated?
And it turned out it was omitted because some guy thought it’s cool to save some characters…
I understand that’s there is always a way to handle it. But ask yourself: Would you like to write inconsistent code with confidence, or you’d prefer monkey patches here and there with thousands of conditions?
Maybe null make life easier when you are not using TypeScript ...
You can use TS without using TS, by typing with JSDocs. Try this out in VSCode:
// @ts-check/**
* @typedef User
* @property {string} id - The user's unique ID.
* @property {string} name - The user's name.
* @property {string} [email] - The user's email address (optional).
*//**
* Get a user by ID.
* @param {string} id
* @returns {User}
*/// Red squiggly here, because we are missing `name`:// But if you miss `email` is ok because that one is "optional"constgetUser=id=>({id});
Thanks a lot for that last line, Roman. I greatly appreciate some good vibes in this comment section ❤️
In my experience, consistency is one of the most important thing
If you really think about it, we can have that same consistency with undefined instead of null. At work we have contracts between the API and the client. We translate the type definitions from the back-end in .d.ts files we can use from TypeScript and JavaScript. When something is optional in the back-end, the type is Type?, not Type | null or Type | null | undefined, so we know with confidence that we can send a value or undefined (namely, not send it). Same applies for the things we get of the back-end, if something we fetch could be missing, then the type is Type? again, which means it can be undefined, and we deal with that either with default values, optional chaining and all that sweet tech we have to deal with nullish values.
Long story short, you have a convention to use null and undefined, that convention could use only undefined as well and work ... and this still doesn't negate the fact that you can use undefined everywhere, except when you talk with the back-end.
I updated the article adding some other folks that have a similar opinion to mine (even a feature request in consideration to update Angular so it only uses undefined).
Offtopic, I was reading your previous article and wondered why to compose functions in this way update('user')({ ...data })(user), looks like you are intentionally keeping all functions with one argument, and I don't understand why not to have a function with 3 arguments instead. I'd love to read a new article from you "You don't need second argument" explaining this pattern. Looks crazy tbh.
Also "You might not need" is a way better naming, because people need classes in Angular, indeed they need mutation in Vue, null in response from database.
Is a pattern called currying, it allows you to create functions from functions and make everything more reusable:
constupdate=property=>value=>object=>({...object,[property]:value,});constupdateName=update("name");constblankName=updateName("");constnameFoo=updateName("foo");constblanks=[{id:1,name:"Luke"},{id:2,name:"Roman"},].map(blankUser);// Returns the array with `name` on both set to ""blanks.map(nameFoo);// Returns the array with `name` on both set to "foo"
Maybe is not the best example, but basically you can create updateX and reuse it all you want, with different values for different objects ... compared to doing the same with 3 arguments:
We need to write update and pass every argument with every use. Currying is one of those things that when it "clicks", you never go back. A popular library that uses this a lot is Rambda.
Also "You might not need" is a way better naming, because people need classes in Angular, indeed they need mutation in Vue, null in response from database.
I considered that rename a few times, but my point is that you don't need it in your own creations, that's the main reason I have my articles set so that is more for "almost experts" than "beginner". Beginners using other folks tools need to learn all this stuff, way later we learn about the things we don't need to code, but might need to interact with somebody else's code.
Thanks, but still more in depth article would be awesome. By your example I can see that in plain JS carrying is worst thing to happen: if you see somewhere in the code nameFoo, you'll probably assume it's a simple string, while actually it's a function which takes object and returns new object with updated name property. With TypeScript it's still very confusing, okay we can hover and see the type, but I assume it will show a complex barely readable type. And I hardly imagining how it is even possible to write all this types, how much complex generics will be needed for this, how much unions.
In TS function "update" has to take a union of all table names. And what if for one table update should have different implementation?
While in true FP languages there is a function overloading and you can define them separately for different tables, which is removing all downsides and sounds awesome. But JS/TS is not such language so it's a good question if the same approaches really worth to be used here? (I didn't ever worked with FP language)
I assume you are following this carrying approach on real projects and you know how to deal with difficulties on this way, so let me repeat myself, isn't it bright idea for article? There are a lot of articles on the topic ofc, but they only sharing basics and explaining nothing useful about how to deal with it in real world, when to use and when to avoid. Not insisting of course, maybe there are such articles and need to search harder and play with it myself
Sure thing! I'll create an article about currying and mention you on it for pushing the idea if you don't mind ^_^ ... about the TS implementation, that update function could look something like this:
constupdate=// Generic for the property name<PropertyextendsPropertyKey>(property:Property)=>// Generic for the value<Value>(value:Value)=>// We expect the "Source" object to be an object of unknown properties<SourceextendsRecord<PropertyKey,unknown>>(object:Source,// We return that Source combined with the new property and value):Source&{[propertyinProperty]:Value}=>({...object,[property]:value,});
I do love to do TS + FP, so creating an article about this might be interesting for other folks as well 😄
1) Json doesn’t have undefined, also when you interact with other sources (db, cache,..)
2) null and undefined are totally different things, used in different cases.
When the value is null, you know it was defined
When the value is undefined, you know it wasn’t defined
Example, a validator should know if a variable is missing (undefined), or it’s presented without value (null)
undefined😉undefined.nullorundefined, so I still don't see much value fornullin this scenario. If a value can either come or not, then you can define it as optional (meaningType | undefined), and in the front end you can just dovalue ?? "default value"orif (value === undefined) { throw // ... }or whatever 😄undefined🎉Json can model undefined through absence of a key. Otherwise fully agree.
When reading a model, I would generally consider absent or undefined values an error, or eg a just in time migration that needs to still happen :)
Otherwise the contract is too fuzzy for my taste
With tech like TS, contracts are actually pretty easy to define. There are tools out there that turn all kind of type definitions in
.d.tsfiles, that you can use from TS or JS with editors such as VSCode to get the type checking. If an API defines something as optional, in TS that'sType | undefined, so you can omit that key or send thatTypeand it will work :D ... if the types change in any way, you should update your type definitions accordingly (using the same tools), and you'll get errors directly in your editor 🎉That’s the thing.
Eg from a json response, a “post” which has “deleted_at” is null means it’s not deleted. But if the “deleted_at” is missing, it should be considered as invalid api response
That depends on the API implementation. This is like saying:
Which is kinda like a Straw Man. If the API has a poor implementation that requires
null, that doesn't mean you need to usenullin your entire codebase, that just means you need to use it in your surface of contact with said API.Sorry but I wouldn’t consider an api which has consistent response for nullable object, as a poor implemented api..
Me neither, that doesn't have anything to do with
null. You can just omit something when is "nullish" which translates to .... you guest it,undefined, and it has the added value that payloads are smaller 🎉Absence and null are different things, as said.
What constraints are you under that these minor payload sizes matter? you’ll probably get more out of gzip than omitting nullish.
Payloads are smaller by omitting some characters, good.
But if it causes confusion between null (value is set as no value) and undefined (value is not set), I wouldn’t take it as a good thing.
"Absence and null are different things" ... how? The size of the payload doesn't only affect the request, also the parsing. You're literally loading something trough the wire and in memory that you could omit, just to use
nullX_XWondering how no one still mentioned 'in' operator:
Btw it’s fun when
Yup, it kinda sucks, but is preferable to return
undefinedfor something that isn't defined ... you can still check if a variable exists before trying to access it, by usingtypeof:Accessing properties is a better DX, and with
?.and??is even better:I’m not talking about the check. I’m just trying to point the inconsistency between two cases about the same thing.
But that's the thing, they aren't the same thing. One is trying to access something not declared in the scope, while the other is trying to access a property of something that is declared in the scope. You can test both with
typeof === "undefined", but if you try to access something undeclared you get an error, which sucks, but that's how it works. Nowadays is very difficult to end up in this scenario tho, because editors will let you know if you're trying to access an undeclared value.You don’t need “in” /s
Several reasons, I guess:
undefinedinvalid, because withinyou can figure out if it was intentionally set asundefined, or is actually missing from the object.null,inwill returntrueas well, so that doesn't make it better thanundefined, nor different. So another argument in favor of "they are the same".in.TL;DR: Because is an argument in favor of the article, I guess I could even update it with that :D
You should explain that to me. I mean you're the ones saying "null means something is defined but intentionally empty", so you're the ones saying there's a need for that kind of thing ... I'm just saying that using
inyou can achieve the same thing withundefined... in both cases we are saying something is "defined but not quite", just with different wording ... and I still don't get why you need that distinction 🤣Well, I tried to find an example but cannot :)
And in this whole long holywar I cannot see a single example why null is better. JSON is one example, but let's imagine we delete all null keys from objects after getting data. Okay now we have libraries which are requiring null, and we have to convert undefined to null when using them. And that's all.
Maybe null make life easier when you are not using TypeScript, I don't know. With TypeScript you can see type in the code, and without it people are probably doing console.log(data) all the time to see data shape, and if values are omitted this is confusing.
Good article, thanks for writing, please keep doing it!
Well not only me, also other people here were trying to explain to you, but seems you just didn’t want to get it.
In my experience, consistency is one of the most important thing. Example: my service exchanges data with other services, they will have the same schema where they agree how an object looks like.
When other service sends me a kebab request with “ketchup” is null, I understand this kebab should be made without ketchup.
If the “ketchup” key is undefined, I understand that this kebab is on hold, because its definition is incomplete.
But if the “ketchup” key is missing? Wtf? Maybe the request was broken in the middle? Maybe the other service updated new schema and changed the key? Maybe the “ketchup” key is deprecated?
And it turned out it was omitted because some guy thought it’s cool to save some characters…
I understand that’s there is always a way to handle it. But ask yourself: Would you like to write inconsistent code with confidence, or you’d prefer monkey patches here and there with thousands of conditions?
Happy Befana btw
You can use TS without using TS, by typing with JSDocs. Try this out in VSCode:
Thanks a lot for that last line, Roman. I greatly appreciate some good vibes in this comment section ❤️
If you really think about it, we can have that same consistency with
undefinedinstead ofnull. At work we have contracts between the API and the client. We translate the type definitions from the back-end in.d.tsfiles we can use from TypeScript and JavaScript. When something is optional in the back-end, the type isType?, notType | nullorType | null | undefined, so we know with confidence that we can send a value orundefined(namely, not send it). Same applies for the things we get of the back-end, if something we fetch could be missing, then the type isType?again, which means it can be undefined, and we deal with that either with default values, optional chaining and all that sweet tech we have to deal with nullish values.Long story short, you have a convention to use
nullandundefined, that convention could use onlyundefinedas well and work ... and this still doesn't negate the fact that you can useundefinedeverywhere, except when you talk with the back-end.I updated the article adding some other folks that have a similar opinion to mine (even a feature request in consideration to update Angular so it only uses
undefined).Offtopic, I was reading your previous article and wondered why to compose functions in this way
update('user')({ ...data })(user), looks like you are intentionally keeping all functions with one argument, and I don't understand why not to have a function with 3 arguments instead. I'd love to read a new article from you "You don't need second argument" explaining this pattern. Looks crazy tbh.Also "You might not need" is a way better naming, because people need classes in Angular, indeed they need mutation in Vue, null in response from database.
Is a pattern called currying, it allows you to create functions from functions and make everything more reusable:
Maybe is not the best example, but basically you can create
updateXand reuse it all you want, with different values for different objects ... compared to doing the same with 3 arguments:We need to write update and pass every argument with every use. Currying is one of those things that when it "clicks", you never go back. A popular library that uses this a lot is Rambda.
I considered that rename a few times, but my point is that you don't need it in your own creations, that's the main reason I have my articles set so that is more for "almost experts" than "beginner". Beginners using other folks tools need to learn all this stuff, way later we learn about the things we don't need to code, but might need to interact with somebody else's code.
Thanks, but still more in depth article would be awesome. By your example I can see that in plain JS carrying is worst thing to happen: if you see somewhere in the code nameFoo, you'll probably assume it's a simple string, while actually it's a function which takes object and returns new object with updated name property. With TypeScript it's still very confusing, okay we can hover and see the type, but I assume it will show a complex barely readable type. And I hardly imagining how it is even possible to write all this types, how much complex generics will be needed for this, how much unions.
In TS function "update" has to take a union of all table names. And what if for one table update should have different implementation?
While in true FP languages there is a function overloading and you can define them separately for different tables, which is removing all downsides and sounds awesome. But JS/TS is not such language so it's a good question if the same approaches really worth to be used here? (I didn't ever worked with FP language)
I assume you are following this carrying approach on real projects and you know how to deal with difficulties on this way, so let me repeat myself, isn't it bright idea for article? There are a lot of articles on the topic ofc, but they only sharing basics and explaining nothing useful about how to deal with it in real world, when to use and when to avoid. Not insisting of course, maybe there are such articles and need to search harder and play with it myself
Sure thing! I'll create an article about currying and mention you on it for pushing the idea if you don't mind ^_^ ... about the TS implementation, that
updatefunction could look something like this:I do love to do TS + FP, so creating an article about this might be interesting for other folks as well 😄
Awesome! No one yet wrote article in my honor, I'm flattered :)
I wish it could explain "Why" and "When to use" no less than "How to use", so pros and cons.
So far carrying looks like a lego and there is something in it, definitely there is something about it.