Control de flujo y manejo de errores

Este articulo necesita una revisión editorial. Cómo puedes ayudar.

Esta traducción está incompleta. Por favor, ayuda a traducir este artículo del inglés.

Javascript soporta un conjunto compacto de sentencias específicas para el manejo de flujo, que pueden ser utilizadas para incorporar mayor interactividad a tus aplicaciones. Este capítulo provee una vista general de las mismas.

La guía de referencia de Javascript contiene detalles exhaustivos sobre las sentencias mencionadas en este capítulo. El punto y coma (;) se utiliza para separar sentencias de código Javascript.

En Javascript cualquier expresión es también una sentencia. Puedes ver Expresiones y operadores para más información sobre expresiones.

Sentencia de bloque

La sentencia de bloque es el tipo de sentencia más básico y se utiliza para agrupar sentencias. El bloque se delimita entre un par de llaves:

{
  sentencia_1;
  sentencia_2;
  .
  .
  .
  sentencia_n;
}

Ejemplo

El bloque es comúnmente utilizado para el control de flujo  (ej. if, for, while).

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

En este caso { x++; } es un bloque de sentencias.

Importante: Javascript no tiene ámbito a nivel bloque en versiones anteriores a ECMAScript 6. Las variables introducidas dentro de un bloque pertenecen a la función o script que lo contiene y el efecto de declararlas persiste más alla del bloque mismo. En otras palabras, los bloques no introducen un nuevo ámbito. Si bien los bloques "standalone" son válidos no deberían ser utilizados en Javascript ya que no se comportan como los bloques de C o Java. Por ejemplo:

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

Este código imprime el número 2  ya que la sentencia de la variable x dentro del bloque está en el mismo ámbito que la variable x definida antes del bloque. En C o Java el equivalente de este código imprimiría 1.

A partir de ECMAScript 6, se introduce el ámbito a nivel bloque utilizando let para declarar las variables.

Sentencias condicionales

Una sentencia condicional es un conjunto de comandos que se ejecutan si una condición es verdadera. JavaScript soporta dos sentencias condicionales: if...else y switch

Sentencia if...else

Se utiliza la sentencia if para comprobar si la condición lógica es verdadera. Se utiliza la opción else para ejecutar un sentencia si la condición es falsa. A continuación se muestra un ejemplo de if...else:

if (condición) {
  sentencia_1;
} else {
  sentencia_2;
}

La condición puede ser cualquier expresión que se evalúa a true o false. Consultar Boolean para una explicación de como se evalua true y false. Si la condición es verdadera, sentencia_1 se ejecutra; si no, sentencia_2 se ejecuta. La sentencia_1 y la sentencia_2 pueden ser cualquier sentencia, incluyendo sentencias anidadas en if.

Tambien puedes componer sentencias más complejas usando else if para tener multiples condiciones, como se muestra a continuación:

if (condición_1) {
  sentencia_1;
} else if (condición_2) {
  sentencia_2;
} else if (condición_n) {
  sentencia_n;
} else {
  ultima_sentencia;
} 

Para ejecutar multiples sentencias, agrupalas dentro de sentencias de bloque ({ ... }) . En general, usar siempre sentencias de bloque es una buena práctica, sobre todo cuando se anidan sentencias if:

if (condición) {
  ejecutar_sentencia_1_si_condición_es_verdadera;
  ejecutar_sentencia_2_si_condición_es_verdadera;
} else {
  ejecutar_sentencia_3_si_condición_es_falsa;
  ejecutar_sentencia_4_si_condición_es_falsa;
}
Se aconseja no usar asiganción simple dentro de una expresión condicional porque dicha asignación puede ser confundida con el comparador de igualdad cuando se lee de pasada el código. Por ejemplo, no uses el siguiente código:
 
if (x = y) {
  /* sentencias aquí */
}

Si necesitas usar una asignación dentro de una expresión de condición, una práctica común es poner paréntesis adicionales alrededor de la asignación. Por ejemplo:

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

Valores falsos:

Los siguientes valores se evaluarán como falso (también conocidos como valores Falsy):

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

El resto de valores, incluidos todos los objetos, son evaluados como verdadero cuando son pasados a una sentencia condicional.

No confundir los valores primitivos booleanos true y false con el verdadero y falso del objeto Boolean. Por ejemplo:

var b = new Boolean(false);
if (b) // Esta condición se evalua a true

Ejemplo

En el siguiente ejemplo, la función comprobarDatos devuelve verdadero si el número de caracteres en un objeto Text es tres; en otro caso, muestra una alerta y devuelve falso.

function comprobarDatos() {
  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;
  }
}

switch

Una sentencia switch permite a un programa evaluar una expresión e intenta igualar el valor de dicha expresión a una etiqueta de caso (case). Si se encuentra una coincidencia, el programa ejecuta la sentencia asociada. Una sentencia switch se describe como se muestra a continuación:

switch (expresión) {
  case etiqueta_1:
    sentencias_1
    [break;]
  case etiqueta_2:
    sentencias_2
    [break;]
    ...
  default:
    sentencias_por_defecto
    [break;]
}

El programa primero busca una claúsula case con una etiqueta que coincida con el valor de la expresión y, entonces, transfiere el control a esa cláusula, ejecutando las sentencias asociadas a ella. Si no se encuentran etiquetas coincidentes, el programa busca la cláusula opcional default y, si se encuentra, transfiere el control a esa cláusula, ejecutando las sentencias asociadas. Si no se encuentra la cláusula default, el programa continúa su ejecución por la siguiente sentencia al final del switch. Por convención, la cláusula por defecto es la última cláusula, aunque no es necesario que sea así.

La sentencia opcional break asociada con cada cláusula case asegura que el programa finaliza la sentencia switch una vez que la sentencia asociada a la etiqueta coincidente es ejecutada y continúa la ejecución por las sentencias siguientes a la sentencia switch. Si se omite la sentencia break, el programa continúa su ejecución por la siguiente sentencia que haya en la sentencia switch.

Ejemplo

En el siguiente ejemplo, si tipoFruta se evalúa como "Plátanos", el programa iguala el valor con el caso "Plátanos" y ejecuta las sentencias asociadas. Cuando se encuentra la sentencia break, el programa termina el switch y ejecuta las sentencias que le siguen. Si la sentencia break fuese omitida, la sentencia para el caso "Cerezas" también sería ejecutada.

switch (tipoFruta) {
  case "Naranjas":
    console.log("Naranjas cuestan 0,59€ el kilo.");
    break;
  case "Manzanas":
    console.log("Manzanas cuestan 0,32€ el kilo.");
    break;
  case "Plátanos":
    console.log("Plátanos cuestan 0,48€ el kilo.");
    break;
  case "Cerezas":
    console.log("Cerezas cuestan 3,00€ el kilo.");
    break;
  case "Mangos":
    console.log("Mangos cuestan 0,56€ el kilo.");
    break;
  case "Papayas":
    console.log("Mangos y papayas cuestan 2,79€ el kilo.");
    break;
  default:
   console.log("Disculpa, no tenemos el tipo de fruta " + fruittype + ".");
}
console.log("¿Te gustaría tomar algo?");

Sentencias de manejo de excepciones

Puedes lanzar excepciones usando la sentencia throw y manejarlas usando las sentencias try...catch.

Tipos de excepciones

Prácticamente cualquier objecto puede ser lanzado en JavaScript. Sin embargo, no todos los objetos lanzados son creados igual. Mientras que es bastante común para lanzar números o strings como errores, frecuentemente son más efectivos utilizar uno de los tipos de excepciones específicamente creados para este proposito:  

Sentencia throw 

Utiliza la sentencia throw  para lanzar una excepción. Cuando lanzas un excepción, se especifica la expresión que contiene el valor para ser lanzado:

throw expresión;

Puedes lanzar cualquier expresión, no solo expresiones de un tipo especifico. En el siguente código lanzamos varias excepciones de varios tipos: 

throw "Error2";   // Tipo string
throw 42;         // Tipo número
throw true;       // Tipo booleano
throw {toString: function() { return "¡Soy un objeto!"; } };
Note: Puedes especificar un objeto cuando lanzas una excepción. A continuación, puedes hacer referencia a las propiedades del objeto en un bloque catch. En el siguiente ejemplo se crea un objeto myUserException del tipo UserException y lo usa en la sentencia throw.
// Crear un tipo de objeto UserException
function UserException (aviso){
  this.aviso=aviso;
  this.nombre="UserException";
}

// Make the exception convert to a pretty string when used as a string 
// (e.g. by the error console)
UserException.prototype.toString = function () {
  return this.nombre + ': "' + this.aviso + '"';
}

// Create an instance of the object type and throw it
throw new UserException("Value too high");

try...catch

La sentencia try...catch marca un bloque de instrucciones a intentar que pueden causar alguna excepción, y declarar una o más respuestas en caso de que una excepción sea arrojada. Si una excepción es arrojada, la sentencia try...catch se encarga de atraparla.

La sentencia try...catch consiste en un bloque try, el cuál contiene una o más instrucciones, y ninguno o varios bloques catch, conteniendo sentencias que especifican que hacer si una excepción es arrojada en un bloque try. Se desea que las instrucciones dentro del bloque try se ejecuten con éxito, de caso contrario caerán en el bloque catch para ser controladas. Si ninguna instrucción dentro del bloque try (o en una función llamada dentro del bloque try) arroja una excepción, el control pasa inmediatamente al bloque catch. Si ninguna excepción es arrojada en el bloque try, el bloque catch es ignorado. Por último se ejecuta el bloque finally luego de que los bloques try y catch hayan sido ejecutados pero antes de las instrucciones que se encuentren luego de la sentencia try...catch.

El siguiente ejemplo usa la sentencia try...catch. El ejemplo llama a una función que retorna el nombre de un mes desde un arreglo basado en un valor pasado como argumento a la función. Si el valor no corresponde con el número de un mes (entre 1 y 12), una excepción es arrojada con el valor "InvalidMonthNo" y las instrucciones en el bloque catch le asignarán a la variable monthName el valor de unknown.

function getMonthName (mo) {
  mo = mo-1; // Ajusta el indice del arreglo para el arreglo de meses (1=Jan, 12=Dec)
  var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul",
                "Aug","Sep","Oct","Nov","Dec"];
  if (months[mo] != null) {
    return months[mo];
  } else {
    throw "InvalidMonthNo"; //Arroja la palabra "InvalidMonthNo" al ocurrir una excepción
  }
}

try { // instrucciones a probar
  monthName = getMonthName(myMonth); // La función puede arrojar una excepción
}
catch (e) {
  monthName = "unknown";
  logMyErrors(e); // Pasa el objeto de la excepción a un manejador de errores
}

El bloque catch

Un bloque catch es usado para manejar todas las excepciones que pueden ser generadas en el bloque try.

catch (catchID) {
  instrucciones
}

El bloque catch especifica un identificar (catchID en la sintaxis anterior) que mantiene el valor especificado por la sentencia thrown; puedes usar este identificador para obtener información acerca de la excepción que fue arrojada. JavaScript crea este identificador cuando ha entrado en el bloque catch; el identificador dura mientras dure el bloque catch; después de que el bloque catch termine su ejecución, el identificador ya no está disponible.

Por ejemplo, el siguiente código arroja una excepción. Cuando la excepción ocurre, el control es transferido al bloque catch.

try {
  throw "myException" // genera una excepción
}
catch (e) {
  // instrucciones para manejar cualquier excepción generada
  logMyErrors(e) // Pasa el objeto de excepción a un manejador de errores
}

El bloque finally

El bloque finally contiene instrucciones para ejecutar luego de la ejecución del bloque try y el bloque catch pero antes de las instrucciones ubicadas luego de la sentencia try...catch. El bloque finally se ejecuta se haya arrojado o no una excepción. Si una excepción es arrojada, las instrucciones en el bloque finally se ejecutan incluso si no existe un bloque catch que maneje la excepción.

Se puede usar el bloque finally para hacer que tu script falle con gracia cuando una excepción ocurre; por ejemplo, puedes tener la necesidad de liberar un recurso que tu script tiene ocupado. El siguiente ejemplo abre un archivo y luego ejecuta instrucciones que usan el archivo (JavaScript del lado del servidor permite acceder a archivos). Si una excepción es arrojada mientras el archivo está abierto, el bloque finally cierra el archivo antes de que el script falle.

openMyFile();
try {
  writeMyFile(theData); // Esto puede arrojar un error
} catch(e) {  
  handleError(e); // Si ocurre un error es manejado
} finally {
  closeMyFile(); // Siempre cierra el recurso
}

Si el bloque finally retorna un valor, este valor se convierte en el valor de retorno de toda la sentencia try-catch-finally, independientemente de cualquier sentencia return en el bloque try y el bloque catch:

function f() {
  try {
    console.log(0);
    throw "bogus";
  } catch(e) {
    console.log(1);
    return true; // Esta sentencia de retorno es suspendida
                 // hasta que el bloque finally esté completo
    console.log(2); // no alcanzable
  } finally {
    console.log(3);
    return false; // sobreescribe la sentencia de retorno anterior
    console.log(4); // no alcanzable
  }
  // "return false" es ejecutada ahora
  console.log(5); // no alcanzable
}
f(); // console 0, 1, 3; retorna false

Sobreescribiendo los valores retornados por el bloque finally también aplica a excepciones arrojadas o relanzadas dentro de un bloque catch:

function f() {
  try {
    throw "bogus";
  } catch(e) {
    console.log('caught inner "bogus"');
    throw e; // Esta sentencia throw es suspendida hasta que
             // el bloque finally se termine de ejecutar
  } finally {
    return false; // Sobreescribe la sentencia throw anterior
  }
  // "return false" es ejecutado ahora
}

try {
  f();
} catch(e) {
  // Esta nunca es encontrada porque la sentencia throw dentro
  // del bloque catch es sobrescrita por la sentencia return
  // en el bloque finally
  console.log('caught outer "bogus"');
}

// SALIDA
// atrapado dentro de "bogus"

Sentencias try...catch anidadas

Es posible anidad una o más sentencias try...catch. Si una sentencia try...catch interna no posee un bloque catch, la sentencia try...catch exterior verifica si el bloque exterior genera una coincidencia.

Utilizando objetos de Error

Dependiendo del tipo de error, es posible usar el 'name' (nombre) y el 'message' (mensaje) propiedades para obtener un mensaje más refinado. La propiedad 'name' provee la clase general del Error(por ejemplo, 'DOMException' or 'Error'), mientras que la propiedad 'message' por lo general provee un breve mensaje que puede ser obtenido convirtiendo el error de object a string.

Si estás arrojando tus propias excepciones, en orden para tomar ventaja de estas propiedades (Como si tu bloque catch no discrimina entre tus propias excepciones y las excepciones del sistema), puedes usar el constructor de Error. Por ejemplo:

function doSomethingErrorProne () {
  if (ourCodeMakesAMistake()) {
    throw (new Error('The message'));
  } else {
    doSomethingToGetAJavascriptError();
  }
}
....
try {
  doSomethingErrorProne();
}
catch (e) {
  console.log(e.name); // logs 'Error'
  console.log(e.message); // logs 'The message' o un error de JavaScript)
}

Promises

Empezando con ECMAScript 6, Javascript gana soporte para Promise objetos que permiten tomar el control del flujo o diferidas operaciones asincronas.

Una Promise puede estar ubicada en estos estados:

  • pending: Estado inicial, ni terminada exitosamente o rechazada.
  • fulfilled: operación exitosa.
  • rejected: operación fallida o rechazada.
  • settled: la Promise ha sido exitosa o rechazada, pero no está pendiente.

Cargando una imagen con XHR

Un simple ejemplo del uso de Promise y XMLHttpRequest es cargar una imagen que está disponible en el repositorio promise-test de MDN GitHub. Puedes verlo también en acción. Cada paso es comentado y permite que sigas la arquitectura de la Promise y XHR de cerca. Aquí está una versión sin comentar, mostrando el flujo de una Promise para que puedas tener una idea:

function imgLoad(url) {
  return new Promise(function(resolve, reject) {
    var request = new XMLHttpRequest();
    request.open('GET', url);
    request.responseType = 'blob';
    request.onload = function() {
      if (request.status === 200) {
        resolve(request.response);
      } else {
        reject(Error('Image didn\'t load successfully; error code:' 
                     + request.statusText));
      }
    };
    request.onerror = function() {
      reject(Error('There was a network error.'));
    };
    request.send();
  });
}

Para información más detallada, visitar Promise como página de referencia.

Etiquetas y colaboradores del documento

 Última actualización por: D4nny-dev,