Have a look at the following code sample:
data.course.enrolments.forEach(enrolment => enrolment.confirm())
Sometimes the data we work with is not predictable, so to avoid errors or crashes we always have to take care of edge cases. The above code makes a lot of assumptions. It assumes that data.course
exists, data.course.enrolments
exists and is an array of objects with a confirm
property which is a valid Function
.
If the data.course
or data.course.enrolments
property is undefined
we get the following TypeError
error:
Uncaught TypeError: Cannot read property enrolments of undefined
Uncaught TypeError: Cannot read property forEach of undefined
If the confirm
property in each enrolment
object in the data.course.enrolments
array is not a valid Function
, we get the following error:
Uncaught TypeError: enrolment.confirm is not a function
Now have a look at this one:
((data.course || {}).enrolments || [])
.forEach(enrolment => enrolment.confirm && enrolment.confirm())
I'll explain how the above snippet works.
data.course || {}
This expression resolves to data.course
if the course
property exists on the data
object, and resolved to an {}
if it doesn't. The reason it resolves to {}
is because of the ||
operator which checks if the left-hand side of the expression (data.course
) is falsy
, and if it is, resolves the expression to the right-hand side ({}
). If it's not falsy
then it resolved the expression to the left-hand side.
(data.course || {}).enrolments || [])
As explained above, the left-hand side will either be data.course
or {}
. Next, we try to access the enrolments
property on the object resolved from the first expression.
enrolment.confirm && enrolment.confirm()
Unlike the ||
operator, the &&
checks if the left-hand side of the expression (enrolment.confirm
) is truthy
, and if it is, resolves the expression to be the right-hand side (enrolment.confirm()
). This means the function enrolment.confirm
will never be executed if the confirm
property is not found on the enrolment
object.
Summary
Taking care of edge cases as you write your code can save you a lot of debugging time, and also prevent unexpected application crashes.
Top comments (6)
FYI. There is an 'Optional Chaining' TC39 proposal that suggests adding this capability as a standard, built-in feature of JS.
Ex
Wow this is interesting. Thanks for sharing this Evan !
Sure thing. The proposal is at Stage 2 which is promising. It means there are devs actively working out the kinks in the spec. If it lands it'll probably be available in a few years.
If you're interested in how new JS features become standards, check out github.com/tc39.
If you have feedback. Jump into the issues for a proposal. There's no gatekeep to participation.
BTW, nice tip 👍. I'll have to put this to use.
Just applied this:
const details = { email, password } && username && { email, password, username };
That's pretty smart. It reminds me of the style vs readability balancing act. And I wonder how often this would come in handy. It would be nice in TypeScript where accessing any possible null types can throw linting errors. I'll use this next time I run into a similar problem. Thanks for sharing!
Thank you so much for this!!