Optional Chaining
Optional chaining is issue #16 on our issue tracker. For context, there have been over 23,000 issues on the TypeScript issue tracker since then.
At its core, optional chaining lets us write code where TypeScript can immediately stop running some expressions if we run into a null
or undefined
.The star of the show in optional chaining is the new ?.
operator for optional property accesses.When we write code like
let x = foo?.bar.baz();
this is a way of saying that when foo
is defined, foo.bar.baz()
will be computed; but when foo
is null
or undefined
, stop what we’re doing and just return undefined
.”
More plainly, that code snippet is the same as writing the following.
let x = (foo === null || foo === undefined) ?
undefined :
foo.bar.baz();
Note that if bar
is null
or undefined
, our code will still hit an error accessing baz
.Likewise, if baz
is null
or undefined
, we’ll hit an error at the call site.?.
only checks for whether the value on the left of it is null
or undefined
- not any of the subsequent properties.
You might find yourself using ?.
to replace a lot of code that performs repetitive nullish checks using the &&
operator.
// Before
if (foo && foo.bar && foo.bar.baz) {
// ...
}
// After-ish
if (foo?.bar?.baz) {
// ...
}
Keep in mind that ?.
acts differently than those &&
operations since &&
will act specially on “falsy” values (e.g. the empty string, 0
, NaN
, and, well, false
), but this is an intentional feature of the construct.It doesn’t short-circuit on valid data like 0
or empty strings.
Optional chaining also includes two other operations.First there’s the optional element access which acts similarly to optional property accesses, but allows us to access non-identifier properties (e.g. arbitrary strings, numbers, and symbols):
/**
* Get the first element of the array if we have an array.
* Otherwise return undefined.
*/
function tryGetFirstElement<T>(arr?: T[]) {
return arr?.[0];
// equivalent to
// return (arr === null || arr === undefined) ?
// undefined :
// arr[0];
}
There’s also optional call, which allows us to conditionally call expressions if they’re not null
or undefined
.
async function makeRequest(url: string, log?: (msg: string) => void) {
log?.(`Request started at ${new Date().toISOString()}`);
// roughly equivalent to
// if (log != null) {
// log(`Request started at ${new Date().toISOString()}`);
// }
const result = (await fetch(url)).json();
log?.(`Request finished at at ${new Date().toISOString()}`);
return result;
}
The “short-circuiting” behavior that optional chains have is limited property accesses, calls, element accesses - it doesn’t expand any further out from these expressions.In other words,
let result = foo?.bar / someComputation()
doesn’t stop the division or someComputation()
call from occurring.It’s equivalent to
let temp = (foo === null || foo === undefined) ?
undefined :
foo.bar;
let result = temp / someComputation();
That might result in dividing undefined
, which is why in strictNullChecks
, the following is an error.
function barPercentage(foo?: { bar: number }) {
return foo?.bar / 100;
// ~~~~~~~~
// Error: Object is possibly undefined.
}
More more details, you can read up on the proposal and view the original pull request.