Node.js, part 9: optional chaining and nullish coalescing — the defensive code you can delete
Part 9from the Node.js series · 24 parts in all
Node 14 landed V8 8.1 and with it two operators that had been stuck in proposal limbo for
years: ?. and ??. They look like syntax sugar and they are —
but they delete a category of code whose absence is hard to notice, because defensive
&& chains hide the difference between "missing" and "false".
Optional chaining stops at the first null
// Before: every level is a chance for "Cannot read property of undefined".
const city = user && user.address && user.address.city;
// After: short-circuits anywhere in the chain.
const city = user?.address?.city;
It works on calls too, which is the case that used to need a type check:
logger?.debug?.('starting') runs nothing if either is missing. One rule to keep
straight, because it is the usual bug: a?.b short-circuits the whole chain to the
right of it, but (a?.b).c does not — the parentheses end the short-circuit and
.c throws.
Nullish coalescing is about null, not falsiness
This is the part that is not just shorter:
const port = config.port || 3000; // 0 and '' and false also become 3000
const port = config.port ?? 3000; // only null / undefined become 3000
// The bug the extra character fixes: a legitimate 0 retry count,
// an empty-but-intentional string, a disabled flag.
const retries = cfg.retries ?? 3;
Those two lines are the whole reason the operator needed its own syntax. ||
conflates "absent" with "falsy", and for numbers and booleans that distinction is real.
Two companions worth learning at the same time
const name = user.nickname ?? user.fullName ?? 'anonymous';
// Assign only if currently null/undefined.
settings.timeout ??= 5000;
// Call only if the function exists.
handler?.(payload);
Together these turn a validation-heavy function into a readable one. The caution that
comes with them: ?. makes missing data silent, so if a null is a bug,
an explicit check that throws will save you more time than the operator saves characters.
Next: request-scoped context, the problem that used to require threading a logger through
every function signature.