Esta noche me apetece un nullish coalescing operator


type coercion

Escribir código es un poco como hablar con máquinas—y suelen ser más lógicas que tú, y de mal genio.

Máquina: «¿Quieres cenar?»

Richard: «Esta noche me apetece un croissant feroz de queso y ternera.»

Máquina: «Te pregunté si quieres cenar—no qué quieres.»

Por suerte, en JavaScript la máquina es un poco más flexible. Si respondes con un nombre de cena, lo toma como un sí y muestra tu cena en pantalla.

  const dinnerOfRichard = 'croissant de queso y ternera' // respondí «croissant de queso y ternera»
  // la máquina decide qué mostrar
  const coercion = (dinnerOfPerson) => {
  // si dinnerOfPerson está vacío—null, undefined o cadena vacía—JavaScript lo trata como false
    if(dinnerOfPerson){
      console.log('Sí, cenas esta noche, y comes:',dinnerOfRichard)
    } else {
      console.log('No cenas')
    }
  }
  coercion(dinnerOfRichard);// pantalla: «Sí, cenas esta noche, y comes: croissant de queso y ternera»
  const dinnerOfJolin = ''// Jolin no respondió
  coercion(dinnerOfJolin);// pantalla: «No cenas»

Si no respondes, o respondes que no (false, 0, cadena vacía, null, undefined), la máquina también acepta esas respuestas y muestra lo que planeaste: «No cenas». Por dentro piensa: «Como no nombraste comida, asumo que no cenas.»

Eso se llama coalescing.

Por eso hay quien dice que el type coercion de JavaScript es demasiado laxo—los bugs cuestan de encontrar. Por otro lado, es un rasgo clave: código corto que encaja con cómo pensamos los humanos.

El type coercion aparece por todas partes en JavaScript, por ejemplo en la lógica de «y» y «o».

«Y» y «o»

«Y» y «o» no son solo de máquinas—los humanos los usamos todo el tiempo. Por ejemplo:

  const isGangGoing = true; // Gang va a hacer senderismo
  const isXienGoing = true; // Xien va a hacer senderismo
  // la máquina decide si yo voy
  const  wantHiking = (isGangGoing,isXienGoing)=>{
    if(isGangGoing && isXienGoing) {
      console.log('Vale, voy a hacer senderismo')
    } else {
      console.log('No voy a hacer senderismo')
    }
  }
  wantHiking(isGangGoing,isXienGoing);
  // pantalla: «Vale, voy a hacer senderismo»

En lenguaje humano: con && como frontera, si Gang va (isGangGoing) y (&&) Xien va (isXienGoing)—ambos true—yo voy; si no, no.

Si cambias && (y) por || (o), queda: si Gang va, o (||) Xien va—cualquiera de los dos—yo voy; si no, no.

Águila
Águila en la montaña Malabang

Lo interesante es que && y || de JavaScript llegan al mismo resultado con un proceso distinto de la intuición humana. Para &&, el pensamiento de JS en palabras humanas queda así:

Si Gang va (isGangGoing es true), entonces si Xien va o no (isXienGoing true o false) decide si yo voy—si Xien va, voy; si no, no. Si Gang no va (isGangGoing es false), da igual si Xien va: yo no voy.

Para || queda:

Si Gang va, yo voy seguro, da igual si Xien va. Si Gang no va, entonces si Xien va decide—si Xien va, voy; si no, no.


Para «y» y «o», JS y los humanos difieren en el proceso de juzgar, pero el resultado es el mismo. Proceso de JS:

  • condición1 && condición2 significa: si la izquierda de && es true, devolver la derecha (condición2); si la izquierda es false, devolver la izquierda.
  • condición1 || condición2 significa: si la izquierda de || es true, devolver la izquierda (condición1); si la izquierda es false, devolver la derecha (condición2).

Resultado de JS:

  • condición1 && condición2 => ambos true → true; cualquiera false → false.
  • condición1 || condición2 => cualquiera true → true; ambos false → false.

Con type coercion, los lados de && y || no tienen que ser true/false en blanco y negro—pueden ser respuestas como «croissant de queso y ternera». Es decir: Richard diciendo no, en blanco, o «no existe Richard» (false, null, undefined, 0, cadena vacía) cuenta como false; cualquier otra respuesta cuenta como true.

Lo más ingenioso: tras juzgar true/false, no devuelve true o false—devuelve lo que hubiera, p. ej. la cadena «croissant de queso y ternera». Eso permite código casi injustamente corto:

  let dinnerOfJolin = `croissant de queso y ternera` // Jolin quiere «croissant de queso y ternera»
  // la máquina muestra el pedido en pantalla
  const coercion = (dinnerOfPerson) => {
     console.log(dinnerOfPerson && `Esta noche me apetece ${dinnerOfPerson}`)
  }
  coercion(dinnerOfJolin); // pantalla: «Esta noche me apetece croissant de queso y ternera», porque Jolin pidió
  dinnerOfJolin = ``// Jolin no dijo nada
  coercion(dinnerOfJolin);// la pantalla no muestra nada—ni «Esta noche me apetece»

En lenguaje humano: si dinnerOfJolin tiene algo («croissant de queso y ternera»), es true, luego devuelve la derecha de && y muestra «Esta noche me apetece croissant de queso y ternera»; si está vacío, no muestra nada, porque devuelve la izquierda—dinnerOfJolin.

|| funciona al revés:

  let dinnerOfJolin = `` // Jolin no dijo nada
  // la máquina muestra el pedido de Jolin en pantalla
  const coercion = (dinnerOfJolin) => {
    console.log(dinnerOfJolin || `Jolin no ha pedido`)
  }
  coercion(dinnerOfJolin);// pantalla: «Jolin no ha pedido»
  dinnerOfJolin = `Esta noche me apetece croissant de queso y ternera`// Jolin dijo «Esta noche me apetece croissant de queso y ternera»
  coercion(dinnerOfJolin);// pantalla: «Esta noche me apetece croissant de queso y ternera»

En lenguaje humano: si dinnerOfJolin está vacío, es false, luego muestra la derecha de ||—«Jolin no ha pedido»; si tiene algo («croissant de queso y ternera»), muestra la izquierda—«Esta noche me apetece croissant de queso y ternera».

nullish coalescing operator

Si has llegado hasta aquí, && y || deberían estar claros—y el ?? de ES2020 (nullish coalescing operator) es fácil. En resumen, sustituye a || en casos especiales.

Por ejemplo: Máquina: «¿Cuánto pagas por la cena?»

Yo quería que la pantalla mostrara «0» cuando respondo descaradamente 0, y «Aún no has respondido» cuando no digo nada.

Pero el || de JS no logra lo que quiero:

  let payOfRichard=0 // respondí 0
  // la máquina muestra cuánto pago
  const coercion = (payOfRichard) => {
    console.log(payOfRichard || `Aún no has respondido`)
  }
  coercion(payOfRichard)// pantalla: «Aún no has respondido»

Eso muestra «Aún no has respondido»—no es lo que queremos; queremos «0». Cambia || por ?? y listo. Abajo, cuando aún no he dicho nada, muestra «Aún no has respondido».

  let payOfRichard; // aún no he dicho nada
  // la máquina muestra cuánto pago
  const nco = (payOfRichard) => {
    console.log(payOfRichard ?? `Aún no has respondido`)
  }
  nco(payOfRichard) // muestra «Aún no has respondido»
  payOfRichard=0 // respondí 0
  nco(payOfRichard) // muestra «0»
  payOfRichard=100
  nco(payOfRichard) // muestra «100»

Con el nullish coalescing operator, otra vez podemos mandar a la máquina con código más corto en casos más complejos. Tres usos habituales:

  1. Quieres no mostrar nada cuando no hay valor, y el valor cuando sí → usa &&.
  2. Quieres un mensaje por defecto cuando no hay valor o el valor es 0, y el valor en caso contrario → usa ||.
  3. Quieres un mensaje por defecto cuando no hay valor, pero cuando hay valor—sea 0 o no—mostrar ese valor → usa ??.

Esa forma de pensar también aparece en la vida humana. Cuando negociaba patentes, con algunos jefes de empresa convenía invitar a la esposa—porque cuando el jefe te llama ya ha decidido solicitar, pero quien controla el dinero quizá aún no. La lógica de máquina que acabamos de aprender, jefe && esposa, aplica: cerrar el trato depende más de ella. Ayudarla a entender qué es una patente y por qué solicitarla parece subir las probabilidades.