Encadenamiento opcional
Las cadenas largas de accesos a propiedades en JavaScript pueden ser propensas a errores, ya que cualquiera de ellas podría evaluarse como null
o undefined
(también conocidos como valores “nulos”). Verificar la existencia de propiedades en cada paso fácilmente se convierte en una estructura profundamente anidada de sentencias if
o en una larga condición if
que replica la cadena de acceso a propiedades:
// Versión propensa a errores, podría lanzar una excepción.
const nameLength = db.user.name.length;
// Menos propenso a errores, pero más difícil de leer.
let nameLength;
if (db && db.user && db.user.name)
nameLength = db.user.name.length;
Lo anterior también puede expresarse usando el operador ternario, lo que no ayuda precisamente a la legibilidad:
const nameLength =
(db
? (db.user
? (db.user.name
? db.user.name.length
: undefined)
: undefined)
: undefined);
Introduciendo el operador de encadenamiento opcional
Seguramente no quieres escribir código como ese, así que tener alguna alternativa es deseable. Algunos otros lenguajes ofrecen una solución elegante a este problema con una característica llamada “encadenamiento opcional”. De acuerdo con una propuesta de especificación reciente, “una cadena opcional es una cadena de uno o más accesos a propiedades y llamadas a funciones, el primero de los cuales comienza con el token ?.
”.
Usando el nuevo operador de encadenamiento opcional, podemos reescribir el ejemplo anterior de la siguiente manera:
// Sigue verificando errores y es mucho más legible.
const nameLength = db?.user?.name?.length;
¿Qué pasa cuando db
, user
, o name
es undefined
o null
? Con el operador de encadenamiento opcional, JavaScript inicializa nameLength
a undefined
en lugar de lanzar un error.
Nota que este comportamiento también es más robusto que nuestra verificación de if (db && db.user && db.user.name)
. Por ejemplo, ¿y si name
siempre estuviera garantizado como una cadena? Podríamos cambiar name?.length
a name.length
. Entonces, si name
fuera una cadena vacía, aún obtendríamos la longitud correcta de 0
. Esto se debe a que la cadena vacía es un valor falsy: se comporta como false
en una cláusula if
. El operador de encadenamiento opcional soluciona esta fuente común de errores.
Formas adicionales de la sintaxis: llamadas y propiedades dinámicas
También hay una versión del operador para llamar métodos opcionales:
// Extiende la interfaz con un método opcional, que está presente
// solo para los usuarios administradores.
const adminOption = db?.user?.validateAdminAndGetPrefs?.().option;
La sintaxis puede sentirse inesperada, ya que ?.()
es el operador real, que aplica a la expresión antes de él.
Hay un tercer uso del operador, a saber, el acceso opcional a propiedades dinámicas, que se realiza mediante ?.[]
. Devuelve el valor referenciado por el argumento dentro de los corchetes, o undefined
si no hay un objeto del cual obtener el valor. Aquí tienes un caso de uso posible, siguiendo el ejemplo anterior:
// Extiende las capacidades del acceso a propiedades estáticas
// con un nombre de propiedad generado dinámicamente.
const optionName = 'optional setting';
const optionLength = db?.user?.preferences?.[optionName].length;
Esta última forma también está disponible para indexar opcionalmente arreglos, por ejemplo:
// Si el `usersArray` es `null` o `undefined`,
// entonces `userName` se evalúa con gracia a `undefined`.
const userIndex = 42;
const userName = usersArray?.[userIndex].name;
El operador de encadenamiento opcional puede combinarse con el operador nullish coalescing ??
cuando se necesita un valor predeterminado que no sea undefined
. Esto permite un acceso seguro a propiedades profundas con un valor predeterminado especificado, abordando un caso de uso común que previamente requería bibliotecas como el _.get
de lodash:
const object = { id: 123, names: { first: 'Alice', last: 'Smith' }};
{ // Con lodash:
const firstName = _.get(object, 'names.first');
// → 'Alice'
const middleName = _.get(object, 'names.middle', '(sin segundo nombre)');
// → '(sin segundo nombre)'
}
{ // Con encadenamiento opcional y nullish coalescing:
const firstName = object?.names?.first ?? '(sin primer nombre)';
// → 'Alice'
const middleName = object?.names?.middle ?? '(sin segundo nombre)';
// → '(sin segundo nombre)';
}
Propiedades del operador de encadenamiento opcional
El operador de encadenamiento opcional tiene algunas propiedades interesantes: cortocircuito, apilamiento y eliminación opcional. Vamos a recorrer cada una de estas con un ejemplo.
Cortocircuito significa no evaluar el resto de la expresión si un operador de encadenamiento opcional termina antes:
// `edad` se incrementa solo si `db` y `usuario` están definidos.
db?.usuario?.crecer(++edad);
Encadenamiento significa que se pueden aplicar más de un operador de encadenamiento opcional en una secuencia de accesos a propiedades:
// Una cadena opcional puede ser seguida por otra cadena opcional.
const longitudDelPrimerNombre = db.usuarios?.[42]?.nombres.primero.longitud;
Aun así, sé considerado al usar más de un operador de encadenamiento opcional en una sola cadena. Si un valor está garantizado a no ser nulo o indefinido, se desaconseja usar ?.
para acceder a sus propiedades. En el ejemplo anterior, se asume que db
siempre está definido, pero db.usuarios
y db.usuarios[42]
pueden no estarlo. Si existe tal usuario en la base de datos, entonces se asume que nombres.primero.longitud
siempre está definido.
Eliminación opcional significa que el operador delete
puede combinarse con una cadena opcional:
// `db.usuario` se elimina solo si `db` está definido.
delete db?.usuario;
Más detalles se pueden encontrar en la sección Semántica de la propuesta.