Optional Chaining
What is it
Accesses an object's property or calls a function. If the object accessed or function called using this operator is undefined
or null
, the expression short circuits and evaluates to undefined
instead of throwing an error
.
When a property is nullish, the expression is NOT evaluated as a whole.
const adventurer = { name: 'Alice', cat: { name: 'Dinah', }, }; adventurer.dog?.name; // undefined, unevaluated
Use Case
Without optional chaining, looking up a deeply-nested subproperty requires validating the references in between.
const nestedProp = obj.first && obj.first.second;
The value of obj.first
is confirmed to be non-nullish before accessing the value of object.first.second
Optional chaining can backfire
Type Mismatch
Optional chaining could incorrectly return undefined
in places where other data type is expected.
// a?.quantity or b?.quantity could be undefined instead of being a number // will result in NaN error const sort = (a,b) => a?.quantity - b?.quantity
Missing Object Attribute
const fetchedData = data?.attribute; someFunction(fetchedData); // silently fails on missing attribute // someFunction may not receiving the data type it expects
Another Example
# services/location_service.rb ## this line calling the Google Service could return a nil Google::PlacesService.new.place_details(place_id) # invoking methods on nil downstream will result in error
Some Google Places API calls failed and returned nil
- which, in the
location_service
, resulted inno method error
because it was invoking a method onnil
Using &.
optional chaining wouldn’t solve this problem because the attribute ['result']
does not exist on a nil
return which means the optional chaining won't solve the core issue of missing attribute, it would silently fail
- Ruby wouldn’t know how to handle a key retrieval or method call on it
Takeaway
Get specific as possible with Error Handling
- Avoid
catch all
without specific error handling - When a real error occurs with missing attributes (or missed fetch call), you won’t know what to look for when optional chaining is used
The solution was to write a rescue
so the exceptions
return empty json
instead
# rescue syntax rescue StandardError => e render json: {} end # in the controller def search_zipcode render json: Services::LocationService.match_zipcode(params[:address].to_s) rescue StandardError => e render json: {} end