Optional chaining ?. y nullish coalescing ?? en JavaScript moderno

ES2020 introdujo dos operadores que resuelven dos problemas muy frecuentes en JavaScript: el acceso seguro a propiedades anidadas que pueden no existir, y los valores por defecto que no se activan de forma errónea con valores falsy válidos. Son pequeños pero cambian cómo se escribe código que trabaja con datos opcionales.

Optional chaining ?.: acceso seguro sin comprobar null

El operador ?. corta la evaluación y devuelve undefined si el valor a la izquierda es null o undefined, en lugar de lanzar un TypeError:

const usuario = {
  nombre: 'Ana',
  direccion: {
    ciudad: 'Madrid',
    coordenadas: null
  }
};

// Sin optional chaining (verboso y frágil):
const cp = usuario && usuario.direccion && usuario.direccion.cp;

// Con optional chaining:
const cp2 = usuario?.direccion?.cp;
console.log(cp2);  // undefined (no lanza error)

const lat = usuario?.direccion?.coordenadas?.lat;
console.log(lat);  // undefined (coordenadas es null)

// También funciona con métodos y acceso con corchetes:
const mayus = usuario?.nombre?.toUpperCase();
console.log(mayus);  // 'ANA'

const datos = null;
console.log(datos?.toString());    // undefined (no TypeError)
console.log(datos?.['propiedad']); // undefined

// Con llamadas a funciones:
const config = { formatear: (x) => x.trim() };
const sinConfig = null;
console.log(config?.formatear('  hola  '));   // 'hola'
console.log(sinConfig?.formatear('texto'));    // undefined

Nullish coalescing ??: valores por defecto sin trampa

El operador ?? devuelve el operando derecho solo cuando el izquierdo es null o undefined. A diferencia del operador ||, no se activa con valores falsy válidos como 0, '' o false:

// El problema con ||:
const timeout = 0;
const t1 = timeout || 3000;  // 3000 (INCORRECTO: 0 es válido)
const t2 = timeout ?? 3000;  // 0    (CORRECTO)

const nombre = '';
const n1 = nombre || 'Anónimo';  // 'Anónimo' (puede ser incorrecto)
const n2 = nombre ?? 'Anónimo';  // ''        (cadena vacía es válida)

const activo = false;
const a1 = activo || true;  // true  (incorrecto si false es intencional)
const a2 = activo ?? true;  // false (correcto)

// null y undefined SÍ activan ??:
console.log(null ?? 'por defecto');      // 'por defecto'
console.log(undefined ?? 'por defecto'); // 'por defecto'

Combinar ?. y ?? juntos

La combinación más potente es usar ?. para navegar sin errores y ?? para dar un valor por defecto cuando el resultado es null o undefined:

function obtenerConfig(opciones) {
  return {
    host: opciones?.servidor?.host ?? 'localhost',
    puerto: opciones?.servidor?.puerto ?? 3000,
    debug: opciones?.modo?.debug ?? false,
    maxConexiones: opciones?.servidor?.maxConexiones ?? 10
  };
}

console.log(obtenerConfig(null));
// { host: 'localhost', puerto: 3000, debug: false, maxConexiones: 10 }

console.log(obtenerConfig({ servidor: { host: 'api.ejemplo.com', puerto: 443 } }));
// { host: 'api.ejemplo.com', puerto: 443, debug: false, maxConexiones: 10 }

Nullish assignment ??=

ES2021 introdujo ??= que asigna el valor solo si la variable es null o undefined. Equivalente a x = x ?? valor:

// ??= asigna solo si null/undefined
let config = null;
config ??= { tema: 'oscuro', idioma: 'es' };
console.log(config);  // { tema: 'oscuro', idioma: 'es' }

config ??= { tema: 'claro' };  // No asigna (config ya tiene valor)
console.log(config);  // { tema: 'oscuro', idioma: 'es' }

// ||= asigna si el valor es falsy (0, '', false, null, undefined)
let nombre = '';
nombre ||= 'Anónimo';  // Asigna ('' es falsy)

// &&= asigna solo si el valor actual es truthy
let activo = true;
activo &&= false;  // Asigna
console.log(activo);  // false

Errores frecuentes

Algunos errores habituales al usar estos operadores:

// ERROR: combinar ?? con || o && sin paréntesis
// const a = null ?? 'def' || 'otro';  // SyntaxError

// Correcto: usar paréntesis explícitos
const a = (null ?? 'def') || 'otro';  // 'def'

// ERROR: creer que ?. evita errores en el propio null
const arr = null;
arr?.length;         // undefined (OK)
arr?.map(x => x);   // undefined (OK)
arr.length;          // TypeError (sin ?.)

// ATENCIÓN: ?. en llamadas a funciones
const obj = { obtener: null };
obj.obtener?.();     // undefined (método es null, no lanza error)
obj.obtener();       // TypeError (método es null, sí lanza error)

Estos dos operadores son especialmente valiosos al trabajar con respuestas de API donde cualquier campo puede ser opcional, con configuraciones que tienen valores por defecto, y en cualquier código que maneje datos de terceros.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP