Control de flujo y manejo de errores

JavaScript admite un compacto conjunto de declaraciones, específicamente declaraciones de control de flujo, que puedes utilizar para incorporar una gran cantidad de interactividad en tu aplicación. Este capítulo proporciona una descripción de estas declaraciones.

La referencia de JavaScript contiene detalles exhaustivos sobre las declaraciones de este capítulo. El carácter de punto y coma (;) se utiliza para separar declaraciones en código JavaScript.

Todas las expresiones e instrucciones de JavaScript también son una declaración. Consulta Expresiones y operadores para obtener información completa sobre las expresiones.

Declaración de bloque

La declaración más básica es una declaración de bloque, que se utiliza para agrupar instrucciones. El bloque está delimitado por un par de llaves:

{
  statement_1;
  statement_2;
  ⋮
  statement_n;
}

Ejemplo

Las declaraciones de bloque se utilizan comúnmente con declaraciones de control de flujo (if, for, while).

while (x < 10) {
  x++;
}

Aquí, { x++; } es la declaración de bloque.

Importante: JavaScript anterior a ECMAScript2015 (6a edición) no tiene ámbito de bloque. En JavaScript más antiguo, las variables introducidas dentro de un bloque tienen como ámbito la función o script que las contiene, y los efectos de establecerlas persisten más allá del bloque en sí mismo. En otras palabras, las declaraciones de bloque no definen un ámbito.

Los bloques "independientes" en JavaScript pueden producir resultados completamente diferentes de los que producirían en C o Java. Por ejemplo:

var x = 1;
{
  var x = 2;
}
console.log(x); // muestra 2

Esto muestra 2 porque la instrucción var x dentro del bloque está en el mismo ámbito que la instrucción var x anterior del bloque. (En C o Java, el código equivalente habría generado 1).

A partir de ECMAScript2015, las declaraciones de variables let y const tienen un ámbito de bloque. Consulta las páginas de referencia de let y const para obtener más información.

Expresiones condicionales

Una expresión condicional es un conjunto de instrucciones que se ejecutarán si una condición especificada es verdadera. JavaScript admite dos expresiones condicionales: if...else y switch.

Expresión if...else

Utiliza la expresión if para ejecutar una instrucción si una condición lógica es true. Utiliza la cláusula opcional else para ejecutar una instrucción si la condición es false.

Una declaración if se ve así:

if (condition) {
  statement_1;
} else {
  statement_2;
}

Aquí, la condition puede ser cualquier expresión que se evalúe como true o false. (Consulta Boolean para obtener una explicación de lo que se evalúa como true y false).

Si condition se evalúa como true, se ejecuta statement_1. De lo contrario, se ejecuta statement_2. statement_1 y statement_2 pueden ser cualquier declaración, incluidas otras declaraciones if anidadas.

También puedes componer las declaraciones usando else if para que se prueben varias condiciones en secuencia, de la siguiente manera:

if (condition_1) {
  statement_1;
} else if (condition_2) {
  statement_2;
} else if (condition_n) {
  statement_n;
} else {
  statement_last;
} 

En el caso de múltiples condiciones, solo se ejecutará la primera condición lógica que se evalúe como true. Para ejecutar múltiples declaraciones, agrúpalas dentro de una declaración de bloque ({ … }).

Mejores prácticas

En general, es una buena práctica usar siempre declaraciones de bloque, especialmente al anidar declaraciones if:

if (condition) {
  statement_1_runs_if_condition_is_true;
  statement_2_runs_if_condition_is_true;
} else {
  statement_3_runs_if_condition_is_false;
  statement_4_runs_if_condition_is_false;
}

No es aconsejable utilizar asignaciones simples en una expresión condicional, porque la asignación se puede confundir con la igualdad al mirar el código.

Por ejemplo, no escribas un código como este:

// Propenso a ser mal interpretado como "x == y"
if (x = y) {
  /* expresiones aquí */
}

Si necesitas usar una tarea en una expresión condicional, una práctica común es poner paréntesis adicionales alrededor de la asignación, así:

if ((x = y)) {
  /* expresiones aquí */
}

Valores falsos

Los siguientes valores se evalúan como false (también conocidos como valores Falsy:

  • false
  • undefined
  • null
  • 0
  • NaN
  • la cadena vacía ("")

Todos los demás valores, incluidos todos los objetos, se evalúan como true cuando se pasan a una declaración condicional.

Precaución: ¡No confundas los valores booleanos primitivos true y false con los valores true y false del objeto Boolean!.

Por ejemplo:

var b = new Boolean(false);
if (b)         // esta condición se evalúa como verdadera
if (b == true) // esta condición se evalúa como false

Ejemplo

En el siguiente ejemplo, la función checkData devuelve true si el número de caracteres en un objeto Text es tres. De lo contrario, muestra una alerta y devuelve false.

function checkData() {
  if (document.form1.threeChar.value.length == 3) {
    return true;
  } else {
    alert(
        'Introduce exactamente tres caracteres. ' +
        `${document.form1.threeChar.value} no es válido.`);
    return false;
  }
}

Declaración switch

Una instrucción switch permite que un programa evalúe una expresión e intente hacer coincidir el valor de la expresión con una etiqueta case. Si la encuentra, el programa ejecuta la declaración asociada.

Una instrucción switch se ve así:

switch (expression) {
  case label_1:
    statements_1
    [break;]
  case label_2:
    statements_2
    [break;]
    …
  default:
    statements_def
    [break;]
}

JavaScript evalúa la instrucción switch anterior de la siguiente manera:

  • El programa primero busca una cláusula case con una etiqueta que coincida con el valor de expresión y luego transfiere el control a esa cláusula, ejecutando las declaraciones asociadas.
  • Si no se encuentra una etiqueta coincidente, el programa busca la cláusula opcional default:
    • Si se encuentra una cláusula default, el programa transfiere el control a esa cláusula, ejecutando las declaraciones asociadas.
    • Si no se encuentra una cláusula default, el programa reanuda la ejecución en la declaración que sigue al final de switch.
    • (Por convención, la cláusula default está escrita como la última cláusula, pero no es necesario que sea así).

Declaraciones break

La declaración opcional break asociada con cada cláusula case asegura que el programa salga de switch una vez que se ejecuta la instrucción coincidente, y luego continúa la ejecución en la declaración que sigue a switch. Si se omite break, el programa continúa la ejecución dentro de la instrucción switch (y evaluará el siguiente case, y así sucesivamente).

Ejemplo

En el siguiente ejemplo, si fruittype se evalúa como 'Bananas', el programa hace coincidir el valor con el caso 'Bananas' y ejecuta la declaración asociada. Cuando se encuentra break, el programa sale del switch y continúa la ejecución de la instrucción que sigue a switch. Si se omitiera break, también se ejecutará la instrucción para case 'Cherries'.

switch (fruittype) {
  case 'Oranges':
    console.log('Las naranjas cuestan $0.59 la libra.');
    break;
  case 'Apples':
    console.log('Las manzanas cuestan $0.32 la libra.');
    break;
  case 'Bananas':
    console.log('Los plátanos cuestan $0.48 la libra.');
    break;
  case 'Cherries':
    console.log('Las cerezas cuestan $3.00 la libra.');
    break;
  case 'Mangoes':
    console.log('Los mangos cuestan $0.56 la libra.');
    break;
  case 'Papayas':
    console.log('Los mangos y las papayas cuestan $2.79 la libra.');
    break;
  default:
   console.log(`Lo sentimos, no tenemos ${fruittype}.`);
}
console.log("¿Hay algo más que quieras?");

Expresiones de manejo de excepciones

Puedes lanzar excepciones usando la instrucción throw y manejarlas usando las declaraciones try...catch.

Tipos de excepciones

Casi cualquier objeto se puede lanzar en JavaScript. Sin embargo, no todos los objetos lanzados son iguales. Si bien es común lanzar números o cadenas como errores, con frecuencia es más efectivo usar uno de los tipos de excepción creados específicamente para este propósito:

  • excepciones ECMAScript
  • La interfaz DOMException representa un evento anormal (llamado excepción) que ocurre como resultado de llamar a un método o acceder a una propiedad de una API web y la interfaz DOMError describe un objeto de error que contiene un nombre de error.

Expresión throw

Utiliza la expresión throw para lanzar una excepción. Una expresión throw especifica el valor que se lanzará:

throw expression;

Puedes lanzar cualquier expresión, no solo expresiones de un tipo específico. El siguiente código arroja varias excepciones de distintos tipos:

throw 'Error2';   // tipo String
throw 42;         // tipo Number
throw true;       // tipo Boolean
throw {toString: function() { return "¡Soy un objeto!"; } };

Nota Puedes especificar un objeto cuando lanzas una excepción. A continuación, puedes hacer referencia a las propiedades del objeto en el bloque catch.

// Crea un objeto tipo de UserException
function UserException(message) {
  this.message = message;
  this.name = 'UserException';
}

// Hacer que la excepción se convierta en una bonita cadena cuando se usa como cadena 
// (por ejemplo, por la consola de errores)
UserException.prototype.toString = function() {
  return `${this.name}: "${this.message}"`;
}

// Crea una instancia del tipo de objeto y tírala
throw new UserException('Valor muy alto');

Declaración try...catch

La declaración try...catch marca un bloque de expresiones para probar y especifica una o más respuestas en caso de que se produzca una excepción. Si se lanza una excepción, la declaración try...catch la detecta.

La declaración try...catch consta de un bloque try, que contiene una o más declaraciones, y un bloque catch, que contiene declaraciones que especifican qué hacer si se lanza una excepción en el bloque try.

En otras palabras, deseas que el bloque try tenga éxito, pero si no es así, deseas que el control pase al bloque catch. Si alguna instrucción dentro del bloque try (o en una función llamada desde dentro del bloque try) arroja una excepción, el control inmediatamente cambia al bloque catch. Si no se lanza ninguna excepción en el bloque try, se omite el bloque catch. El bloque finalmente se ejecuta después de que se ejecutan los bloques try y catch, pero antes de las declaraciones que siguen a la declaración try...catch.

El siguiente ejemplo usa una instrucción try...catch. El ejemplo llama a una función que recupera el nombre de un mes de un arreglo en función del valor pasado a la función. Si el valor no corresponde a un número de mes (1-12), se lanza una excepción con el valor "InvalidMonthNo" y las declaraciones en el bloque catch establezca la variable monthName en 'unknown'.

function getMonthName(mo) {
  mo = mo - 1; // Ajusta el número de mes para el índice del arreglo (1 = Ene, 12 = Dic)
  let months = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul',
                'Ago', 'Sep', 'Oct', 'Nov', 'Dic'];
  if (months[mo]) {
    return months[mo];
  } else {
    throw 'InvalidMonthNo'; // aquí se usa la palabra clave throw
  }
}

try { // declaraciones para try
  monthName = getMonthName(myMonth); // la función podría lanzar una excepción
}
catch (e) {
  monthName = 'unknown';
  logMyErrors(e); // pasar el objeto exception al controlador de errores (es decir, su propia función)
}

El bloque catch

Puedes usar un bloque catch para manejar todas las excepciones que se puedan generar en el bloque try.

catch (catchID) {
  instrucciones
}

El bloque catch especifica un identificador (catchID en la sintaxis anterior) que contiene el valor especificado por la expresión throw. Puedes usar este identificador para obtener información sobre la excepción que se lanzó.

JavaScript crea este identificador cuando se ingresa al bloque catch. El identificador dura solo la duración del bloque catch. Una vez que el bloque catch termina de ejecutarse, el identificador ya no existe.

Por ejemplo, el siguiente código lanza una excepción. Cuando ocurre la excepción, el control se transfiere al bloque catch.

try {
  throw 'myException'; // genera una excepción
}
catch (err) {
  // declaraciones para manejar cualquier excepción
  logMyErrors(err);    // pasa el objeto exception al controlador de errores
}

Mejores prácticas: Cuando se registran errores en la consola dentro de un bloque catch, se usa console.error() en lugar de console.log() aconsejado para la depuración. Formatea el mensaje como un error y lo agrega a la lista de mensajes de error generados por la página.

El bloque finally

El bloque finally contiene instrucciones que se ejecutarán después que se ejecuten los bloques try y catch. Además, el bloque finally ejecuta antes el código que sigue a la declaración try...catch...finally.

También es importante notar que el bloque finally se ejecutará independientemente de que se produzca una excepción. Sin embargo, si se lanza una excepción, las declaraciones en el bloque finally se ejecutan incluso si ningún bloque catch maneje la excepción que se lanzó.

Puedes usar el bloque finally para hacer que tu script falle correctamente cuando ocurra una excepción. Por ejemplo, es posible que debas liberar un recurso que tu script haya inmovilizado.

El siguiente ejemplo abre un archivo y luego ejecuta declaraciones que usan el archivo. (JavaScript de lado del servidor te permite acceder a los archivos). Si se lanza una excepción mientras el archivo está abierto, el bloque finally cierra el archivo antes de que falle el script. Usar finally aquí asegura que el archivo nunca se deje abierto, incluso si ocurre un error.

openMyFile();
try {
  writeMyFile(theData); // Esto puede arrojar un error
} catch(e) {  
  handleError(e); // Si ocurrió un error, manéjalo
} finally {
  closeMyFile(); // Siempre cierra el recurso
}

Si el bloque finally devuelve un valor, este valor se convierte en el valor de retorno de toda la producción de try…catch…finally, independientemente de las declaraciones return en los bloques try y catch:

function f() {
  try {
    console.log(0);
    throw 'bogus';
  } catch(e) {
    console.log(1);
    return true;    // esta declaración de retorno está suspendida
                    // hasta que el bloque finally se haya completado
    console.log(2); // no alcanzable
  } finally {
    console.log(3);
    return false;   // sobrescribe el "return" anterior
    console.log(4); // no alcanzable
  }
  // "return false" se ejecuta ahora  
  console.log(5);   // inalcanzable
}
console.log(f()); // 0, 1, 3, false

La sobrescritura de los valores devueltos por el bloque finally también se aplica a las excepciones lanzadas o relanzadas dentro del bloque catch:

function f() {
  try {
    throw 'bogus';
  } catch(e) {
    console.log('captura "falso" interno');
    throw e; // esta instrucción throw se suspende hasta 
             // que el bloque finally se haya completado
  } finally {
    return false; // sobrescribe el "throw" anterior
  }
  // "return false" se ejecuta ahora
}

try {
  console.log(f());
} catch(e) {
  // ¡esto nunca se alcanza! 
  // mientras se ejecuta f(), el bloque `finally` devuelve false, 
  // que sobrescribe el `throw` dentro del `catch` anterior
  console.log('"falso" externo capturado');
}

// Produce
// "falso" interno capturado
// false

Declaraciones try...catch anidadas

Puedes anidar una o más declaraciones try...catch.

Si un bloque try interno no tiene un bloque catch correspondiente:

  1. debe contener un bloque finally, y
  2. el bloque catch adjunto de la declaración try...catch se comprueba para una coincidencia.

Para obtener más información, consulta bloques try anidados en la una página de referencia try...catch.

Utilizar objetos Error

Dependiendo del tipo de error, es posible que puedas utilizar las propiedades name y message para obtener un mensaje más refinado.

La propiedad name proporciona la clase general de Error (tal como DOMException o Error), mientras que message generalmente proporciona un mensaje más conciso que el que se obtendría al convertir el objeto error en una cadena.

Si estás lanzando tus propias excepciones, para aprovechar estas propiedades (por ejemplo, si tu bloque catch no discrimina entre tus propias excepciones y las del sistema), puedes usar el constructor Error.

Por ejemplo:

function doSomethingErrorProne() {
  if (ourCodeMakesAMistake()) {
    throw (new Error('El mensaje'));
  } else {
    doSomethingToGetAJavascriptError();
  }
}
⋮
try {
  doSomethingErrorProne();
} catch (e) {               // AHORA, en realidad usamos `console.error()`
  console.error(e.name);    // registra 'Error'
  console.error(e.message); // registra 'The message' o un mensaje de error de JavaScript
}