Capítulo 8
Bugs y manejo de errores

La depuración es dos veces más difícil que escribir código. Por lo tanto, si usted escribe su código lo mejor que puede, por definición, usted no es lo suficientemente inteligente como para depurar lo que escribió.

Brian Kernighan and P.J. Plauger, The Elements of Programming Style

Yuan-Ma había escrito un pequeño programa que usaba muchas variables globales y atajos de mala calidad. Al leerlo, un estudiante le preguntó: 'Nos advirtió contra estas técnicas, sin embargo usted las usa en su programa. ¿Cómo puede ser? "El maestro respondió:" No hay necesidad de buscar una manguera de agua si la casa no está en llamas ".

Master Yuan-Ma, The Book of Programming

Un programa es un pensamiento cristalizado. A veces son pensamientos con errores, otras veces los errores se producen al convertir nuestros pensamientos en código. De cualquier manera, el resultado es un programa defectuoso.

Las fallas en un programa se llaman generalmente bugs. Los bugs pueden ser errores de programación o problemas en otros sistemas con los que nuestro programa interactúa. Algunos son inmediatamente evidentes, mientras que otros son sutiles y pueden permanecer ocultos en un sistema durante años.

A menudo, los problemas solamente surgen cuando un programa encuentra una situación que el programador no consideró originalmente. A veces tales situaciones son inevitables. Cuando se le pide al usuario que ingrese su edad (“your age_”) y este tipea naranja (_“orange”), esto pone a nuestro programa a prueba. Está situación tiene que ser anticipada y manejada de alguna manera.

Errores de programación

Cuando se trata de errores del programador, nuestro tarea es simple: encontrarlos y arreglarlos. Tales errores pueden ir desde simples errores de tipeo, que causan que la computadora nos avice tan pronto como pone los ojos en nuestro programa, a errores sutiles a nuestra comprensión de la forma en que el programa opera, causando resultados incorrectos sólo en situaciones específicas. Los errores de este último tipo pueden tomar semanas para manifestarse.

El grado en que los lenguajes le ayudan a encontrar tales errores varía. No es sorprendente que JavaScript esté en el extremo de apenas ayuda de esa escala. Algunos lenguajes quieren saber los tipos de todas sus variables y expresiones antes incluso de ejecutar un programa y le dirán de inmediato cuando un tipo se utiliza de una manera inconsistente. JavaScript considera los tipos sólo cuando realmente ejecuta el programa, e incluso entonces, le permite hacer algunas cosas claramente absurdas sin quejarse, como x = true * "monkey".

Sí hay algunas cosas que JavaScript no permite hacer. Escribir un programa que no es sintácticamente válido inmediatamente provocará un error. Otras cosas, como llamar a algo que no es una función o buscar una propiedad en un valor indefinido, provocará que se informe un error cuando el programa se está ejecutando y encuentra dicha acción sin sentido.

Pero muchas veces, un cálculo sin sentido simplemente producirá un NaN (no un número) o undefined (valor indefinido). Y el programa continúa feliz, convencido de que está haciendo algo significativo. El error se manifestará sólo más tarde, después de que el valor falso haya recorrido varias funciones. No desencadena un error, pero en silencio hace que el resultado del programa sea incorrecto. Encontrar la fuente de tales problemas puede ser difícil.

El proceso de encontrar errores (bugs) en los programas se llama depuración (debugging).

Modo estricto (Strict mode)

JavaScript se puede hacer un poco más estricto al permitir el modo estricto. Esto se hace poniendo la cadena "use strict" en la parte superior de un archivo o en el cuerpo de función. He aquí un ejemplo:

function canYouSpotTheProblem() {
  "use strict";
  for (counter = 0; counter < 10; counter++)
    console.log("Happy happy");
}

canYouSpotTheProblem();
// → ReferenceError: counter is not defined

Normalmente, cuando te olvidas de poner var delante de tu variable, como counter en el ejemplo, JavaScript crea una variable global en silencio y usa eso. En el modo estricto, sin embargo, un error se informa en su lugar. Esto es muy útil. Debe tenerse en cuenta, sin embargo, que esto no funciona cuando la variable en cuestión ya existe como global, sólo funciona cuando no existe y la asignación que hemos hecho la habría creado.

Otro cambio en modo estricto es que this mantiene el valor undefined en funciones que no se llaman como métodos. Al hacer una llamada fuera del modo estricto, this se refiere al objeto de alcance global. Así que si accidentalmente llama a un método o constructor incorrectamente en modo estricto, JavaScript producirá un error tan pronto como intente leer algo de esto, en lugar de trabajar felizmente con el objeto global, crear y leer variables globales.

Por ejemplo, considere el siguiente código, que llama a un constructor sin la palabra clave new, de modo que this no se refiere al objeto recien construído:

function Person(name) { this.name = name; }
var ferdinand = Person("Ferdinand"); // oops
console.log(name);
// → Ferdinand

Así que la llamada falsa a Person tuvo éxito pero devolvió un valor undefined y creó el nombre de la variable en forma global. En modo estricto, el resultado es diferente.

"use strict";
function Person(name) { this.name = name; }
// Oops, forgot 'new'
var ferdinand = Person("Ferdinand");
// → TypeError: Cannot set property 'name' of undefined

Nos dice inmediatamente que algo está mal. Esto es muy útil.

El modo estricto (Strict mode) hace algunas cosas más: Se prohíbe dar a una función múltiples parámetros con el mismo nombre y elimina completamente ciertas características problemáticas del lenguaje (como la declaración which, que es tan equivocada que no volveré a mencionarla en este libro).

En pocas palabras, poner un use strict en la parte superior de su programa rara vez causa daño y puede ayudarle a detectar un problema.

Tests

Si el lenguaje no va a hacer mucho para ayudarnos a encontrar errores, tendremos que encontrarlos de la manera más difícil: ejecutando el programa y ver si hace lo correcto.

Hacer esto a mano, una y otra vez, es una manera segura de volverse loco. Afortunadamente, a menudo es posible escribir un segundo programa que automatice las pruebas de su programa.

Como ejemplo, volvemos a utilizar el tipo Vector.

function Vector(x, y) {
  this.x = x;
  this.y = y;
}
Vector.prototype.plus = function(other) {
  return new Vector(this.x + other.x, this.y + other.y);
};

Escribiremos un programa para comprobar que nuestra implementación de Vector funciona como se pretende. Luego, cada vez que cambiamos la implementación, seguimos ejecutando el programa de prueba para que podamos estar razonablemente seguros de que no rompimos nada. Cuando agregamos funcionalidad adicional (por ejemplo, un nuevo método) al tipo Vector, también agregamos pruebas para la nueva característica.

function testVector() {
  var p1 = new Vector(10, 20);
  var p2 = new Vector(-10, 5);
  var p3 = p1.plus(p2);

  if (p1.x !== 10) return "fail: x property";
  if (p1.y !== 20) return "fail: y property";
  if (p2.x !== -10) return "fail: negative x property";
  if (p3.x !== 0) return "fail: x from plus";
  if (p3.y !== 25) return "fail: y from plus";
  return "everything ok";
}
console.log(testVector());
// → everything ok

Escribir pruebas como ésta tiende a producir código bastante repetitivo, incómodo. Afortunadamente, existen piezas de software que ayudan a crear y ejecutar conjuntos de pruebas (test suites) proporcionando un lenguaje (en forma de funciones y métodos) adecuado para expresar las pruebas y para emitir información puntual cuando una prueba falla. Estos se llaman testing frameworks.

Debugging

Una vez que usted nota que hay algo mal con su programa porque se comporta mal o produce errores, el siguiente paso es averiguar cuál es el problema.

A veces es obvio. El mensaje de error apuntará en una línea específica de su programa y si observa la descripción del error y esa línea de código, a menudo puede ver el problema.

Pero no siempre. A veces la línea que desencadenó el problema es simplemente el primer lugar donde se utiliza de manera no válida un valor erroneo que fue producido en otro lugar. Y a veces no hay ningún mensaje de error en absoluto, sólo un resultado no válido. Si ha estado resolviendo los ejercicios en los capítulos anteriores, es probable que ya haya experimentado tales situaciones.

El programa de ejemplo siguiente intenta convertir un número entero en una cadena en cualquier base (decimal, binario, etc.) seleccionando repetidamente el último dígito y luego dividiendo el número para eliminar este dígito. Pero la valor de retorno ridículo que produce actualmente sugiere que tiene un error (bug).

function numberToString(n, base) {
  var result = "", sign = "";
  if (n < 0) {
    sign = "-";
    n = -n;
  }
  do {
    result = String(n % base) + result;
    n /= base;
  } while (n > 0);
  return sign + result;
}
console.log(numberToString(13, 10));
// → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…

Incluso si usted ya noto cual es el problema, finja por un momento que aun no lo a descubierto. Sabemos que nuestro programa está funcionando mal, y queremos averiguar por qué.

Aquí es donde debe resistir el impulso de comenzar a hacer cambios aleatorios en el código. Trate de analizar lo que está sucediendo y llegar a una teoría de por qué podría estar sucediendo. Luego, haga observaciones adicionales para probar esta teoría o, si aún no tiene una teoría, haga observaciones adicionales que podrían ayudarle a encontrar una.

Poner unas pocas llamadas console.log estratégicas en el programa es una buena manera de obtener información adicional sobre lo que está haciendo el programa. En este caso, queremos que n tome los valores 13, 1 y luego 0. Vamos a escribir su valor al inicio del bucle.

13
1.3
0.13
0.013
…
1.5e-323

Right. Dividir 13 por 10 no produce un número entero. En lugar de n / = base, lo que realmente queremos es n = Math.floor (n / base) para que el número se desplace correctamente a la derecha.

Una alternativa al uso de console.log es utilizar las capacidades de depuración de su navegador. Los navegadores modernos vienen con la capacidad de establecer un punto de interrupción en una línea específica de su código. Esto hará que la ejecución del programa se detenga cada vez que se alcanza la línea con el punto de interrupción y le permite inspeccionar los valores de las variables en ese punto. No entraré en detalles aquí ya que los depuradores difieren de navegador a navegador, pero busque en las herramientas de desarrollo de su navegador y busque en la Web más información. Otra forma de establecer un punto de interrupción es incluir una declaración de depurador (que consiste simplemente en la palabra clave debugger) en su programa. Si las herramientas de desarrollo de su navegador están activas, el programa se detendrá cada vez que llegue a esa declaración, y podrá inspeccionar su estado.

Propagación de errores

No todos los problemas pueden ser prevenidos por el programador. Si su programa se comunica con el mundo exterior de alguna manera, existe la posibilidad de que la entrada que obtenga sea inválida o que otros sistemas con los que intente hablar estén rotos o inaccesibles.

Los programas sencillos, o programas que se ejecutan sólo bajo su supervisión, pueden permitirse el lujo de dejar de funcionar cuando se produce un problema de este tipo. Examinarás el problema y lo intentarás de nuevo. Pero las aplicaciones "reales" no se pueden simplemente desconectar. A veces lo correcto es tomar la entrada mala y seguir corriendo. En otros casos, es mejor informar al usuario de lo que salió mal y luego desconectar. Pero en cualquier situación, el programa tiene que hacer algo activamente en respuesta al problema.

Digamos que tiene una función promptInteger que pide al usuario un número entero y lo devuelve. Pero ¿Qué debe devolver si el usuario ingresa una palabra, por ejemplo confunde "your age" por “_orange_”?

Una opción es hacer que devuelva un valor especial. Las opciones comunes para estos valores son null y no undefined.

function promptNumber(question) {
  var result = Number(prompt(question, ""));
  if (isNaN(result)) return null;
  else return result;
}

console.log(promptNumber("How many trees do you see?"));

Esta es una buena estrategia. Ahora cualquier código que llama a promptNumber debe comprobar si se ha leído un número real y, de no ser así, debe reiniciar el proceso de alguna manera, tal vez preguntando de nuevo o rellenando con un valor predeterminado. O podría devolver un valor especial a su llamador (caller) para indicar que no pudo hacer lo que se le pidió.

En muchas situaciones, sobre todo cuando los errores son comunes y el metodo caller debe tenerlo explícitamente en cuenta, devolver un valor especial es una manera prolija de indicar un error. Sin embargo, tiene sus desventajas. Primero, ¿qué pasa si la función ya puede devolver todo tipo posible de valores? Para esta función, es difícil encontrar un valor especial que pueda distinguirse de un resultado válido.

La segunda cuestión con la devolución de valores especiales es que puede conducir a algún código muy abarrotado. Si un pedazo de código llama a promptNumber 10 veces, tiene que comprobar 10 veces si se devolvió null. Y si su respuesta a encontrar null es simplemente devolver null a sí mismo, el llamador (caller) a su vez tendrá que comprobarlo, y así sucesivamente.

Excepciones

Cuando una función no puede proceder normalmente, lo que nos gustaría hacer es simplemente detener lo que estamos haciendo y trabajar en un contexto donde sabemos cómo manejar el problema. Esto es el “manejo de excepciones”.

Las excepciones (exceptions) son un mecanismo que hace posible que el código que se ejecuta en un problema lance (throw) una excepción, que es simplemente lanzar un valor. Elevar una excepción se asemeja en cierta medida a un retorno súper cargado de una función: se eleva no sólo de la función actual, sino también de sus llamantes, hasta la primera llamada que inició la ejecución actual. Esto se llama desenrollar la pila (unwinding the stack). Recuerde el concepto de “pila de llamadas de función” en el Capítulo 3. Una excepción reduce el zoom de esta pila, eliminando todos los contextos de llamada que encuentra.

Si las excepciones siempre estan cerca de la parte inferior de la pila, no serían de mucha utilidad, pues sólo proporcionaría una nueva manera de lanzar su programa. Su poder reside en el hecho de que usted puede establecer "obstáculos" a lo largo de la pila para capturar la excepción. Entonces usted puede actuar, después de que el programa continúa funcionando en el punto donde registró la excepción.

He aquí un ejemplo:

function promptDirection(question) {
  var result = prompt(question, "");
  if (result.toLowerCase() == "left") return "L";
  if (result.toLowerCase() == "right") return "R";
  throw new Error("Invalid direction: " + result);
}

function look() {
  if (promptDirection("Which way?") == "L")
    return "a house";
  else
    return "two angry bears";
}

try {
  console.log("You see", look());
} catch (error) {
  console.log("Something went wrong: " + error);
}

La palabra clave throw se utiliza para generar una excepción. Capturar una se hace envolviendo (wrapping) un pedazo de código en un bloque try, seguido por la palabra clave catch. Cuando el código del bloque try genera una excepción, se evalúa el bloque catch. El nombre de la variable (entre paréntesis) después de la captura se enlazará al valor de la excepción. Una vez finalizado el bloqueo del bloque (o si el bloque try termina sin problemas), el control se desarrolla debajo de toda la sentencia try/catch.

En este caso, utilizamos el constructor Error para crear nuestro valor de excepción (new Error). . Se trata de un constructor de JavaScript estándar que crea un objeto con una propiedad de mensaje. En los entornos de JavaScript modernos, las instancias de este constructor también recopilan información sobre la pila de llamadas que existía cuando se creó la excepción, un denominado seguimiento de la pila (stack trace). Esta información se almacena en la propiedad de la pila y puede ser útil al intentar depurar un problema: nos indica la función exacta donde ocurrió el problema y cuáles otras funciones llevaron a la llamada que falló.

Tenga en cuenta que en nuestro ejemplo la función look ignora por completo la posibilidad de que promptDirection pueda salir mal. Esta es la gran ventaja de las excepciones: el código de manejo de errores sólo es necesario en el punto donde se produce el error y en el punto donde se maneja. Las funciones intermedias pueden desentenderse de todo. Bueno, casi...

Limpieza después de excepciones

Considere la siguiente situación: una función, withContext, quiere asegurarse de que, durante su ejecución, la variable de nivel superior context contiene un valor específico. Después de que termina, restaura esta variable a su valor anterior.

var context = null;

function withContext(newContext, body) {
  var oldContext = context;
  context = newContext;
  var result = body();
  context = oldContext;
  return result;
}

¿Qué pasa si body plantea una excepción? En ese caso, la llamada a withContext será eliminada de la pila por la excepción, y context nunca volverá a su valor anterior.

Hay una característica más que tiene try. Puede ser seguida por un bloque finally en lugar de, o además de, un bloque catch. Un bloque finally dice "No importa lo que pase, ejecute este código después de intentar ejecutar el código en el bloque try". Si una función tiene que limpiar algo, el código de limpieza normalmente se debe poner en el bloque finally.

function withContext(newContext, body) {
  var oldContext = context;
  context = newContext;
  try {
    return body();
  } finally {
    context = oldContext;
  }
}

Tenga en cuenta que ya no tenemos que almacenar el resultado del body (que queremos devolver) en una variable. Incluso si regresamos directamente desde el bloque try, se ejecutará el bloque finally. Podemos hacerlo y estar seguros:

try {
  withContext(5, function() {
    if (context < 10)
      throw new Error("Not enough context!");
  });
} catch (e) {
  console.log("Ignoring: " + e);
}
// → Ignoring: Error: Not enough context!

console.log(context);
// → null

A pesar de que la función llamada por withContext explotó, en withContext se limpió adecuadamente la variable context.

Selective catching

Cuando una excepción hace todo el camino a la parte inferior de la pila sin ser capturada, se maneja por el medio ambiente. Lo que esto significa difiere entre entornos. En los navegadores, normalmente se escribe una descripción del error en la consola de JavaScript (accesible a través del menú Herramientas o Desarrollador del navegador).

Para los errores o problemas del programador que el programa no puede manejar, simplemente dejar pasar el error es lo correcto a menudo. Una excepción no tratada es una forma razonable de señalar un programa roto, y la consola de JavaScript, en navegadores modernos, le proporcionará cierta información sobre qué llamadas de función estaban en la pila cuando ocurrió el problema.

Para los problemas que se espera que sucedan durante el uso rutinario, estrellarse con una excepción no tratada no es una respuesta amigable.

Los usos no válidos del lenguaje, como hacer referencia a una variable inexistente, buscar una propiedad en null o llamar a algo que no es una función, también darán lugar a que se generen excepciones. Tales excepciones se pueden capturar igual que sus propias excepciones.

Cuando se introduce un catch, todo lo que sabemos es que algo en nuestro try causó una excepción. Pero no sabemos qué, ni qué excepción causó.

JavaScript (en una omisión bastante flagrante) no proporciona soporte directo para capturar selectivamente las excepciones: o bien las captura todas o no las captura. Esto hace que sea muy fácil asumir que la excepción que se obtiene es la misma que la que se estaba pensando cuando escribió el bloque catch.

Pero puede que no lo sea. Es posible que se haya violado alguna otra suposición o que haya introducido un error en algún lugar que esté causando una excepción. He aquí un ejemplo, que intenta seguir llamando a promptDirection hasta obtener una respuesta válida:

for (;;) {
  try {
    var dir = promtDirection("Where?"); // ← typo!
    console.log("You chose ", dir);
    break;
  } catch (e) {
    console.log("Not a valid direction. Try again.");
  }
}

El constructo for (;;) es una forma de crear intencionalmente un bucle que no termina por sí solo. Salimos del bucle sólo cuando se da una dirección válida. Pero hemos escrito erróneamente promptDirection, lo que resultará en un error de "variable indefinida". Debido a que el bloque de catch ignora completamente su valor de excepción (e), suponiendo que sabe cuál es el problema, trata erróneamente el error de variable como indicativo de entrada incorrecta. Esto no sólo causa un bucle infinito, sino que también "entierra" el útil mensaje de error sobre la variable mal escrita.

AComo regla general, no cubra las excepciones de captura a menos que sea con el propósito de "enrutarlas" en alguna parte, por ejemplo, a través de la red para decirle a otro sistema que nuestro programa se ha estrellado. E incluso entonces, piense cuidadosamente en cómo podría estar ocultando información.

Así que queremos capturar un tipo específico de excepción. Podemos hacer esto comprobando en el bloque catch si la excepción que conseguimos es la que buscamos y probar de otra manera. Pero, ¿cómo reconocemos una excepción?

Por supuesto, podríamos igualar su propiedad de mensaje con el mensaje de error que esperamos. Pero esa es una manera inestable de escribir código: usariamos la información destinada al consumo humano (el mensaje) para tomar una decisión programática. Tan pronto como alguien cambia (o traduce) el mensaje, el código dejará de funcionar.

Más bien, definamos un nuevo tipo de error y usamos instanceof para identificarlo.

function InputError(message) {
  this.message = message;
  this.stack = (new Error()).stack;
}
InputError.prototype = Object.create(Error.prototype);
InputError.prototype.name = "InputError";

El prototipo se crea al derivar de Error.prototype para que instanceof Error también devuelva true para los objetos InputError. También se le da una propiedad de nombre (name) ya que los tipos de error estándar (Error, SyntaxError, ReferenceError, etc.) también tienen dicha propiedad.

La asignación a la propiedad stack intenta dar a este objeto un rastreo de pila algo útil (en plataformas que lo soportan) creando un objeto de error regular y luego utilizando la propiedad stack de ese objeto como propio.

Ahora promptDirection puede lanzar tal error.

function promptDirection(question) {
  var result = prompt(question, "");
  if (result.toLowerCase() == "left") return "L";
  if (result.toLowerCase() == "right") return "R";
  throw new InputError("Invalid direction: " + result);
}

Y el bucle for puede atraparlo con más cuidado.

for (;;) {
  try {
    var dir = promptDirection("Where?");
    console.log("You chose ", dir);
    break;
  } catch (e) {
    if (e instanceof InputError)
      console.log("Not a valid direction. Try again.");
    else
      throw e;
  }
}

Esto captura sólo instancias de InputError y permite excepciones a través de throw. Si reintroduce el error de tipeo, el error de variable undefined se informará correctamente.

Assertions

Las afirmaciones son una herramienta para comprobar la salud básica de los errores de programación. Considere la función auxiliar assert:

function AssertionFailed(message) {
  this.message = message;
}
AssertionFailed.prototype = Object.create(Error.prototype);

function assert(test, message) {
  if (!test)
    throw new AssertionFailed(message);
}

function lastElement(array) {
  assert(array.length > 0, "empty array in lastElement");
  return array[array.length - 1];
}

Esto proporciona una manera compacta de cumplir las expectativas, ayudando a alertar del programa si la condición declarada no se cumple. Por ejemplo, la función lastElement, que recupera el último elemento de una matriz, retornaría indefinidamente arrays vacíos si la aserción (assertion) se omitiera. Obtener el último elemento de una matriz vacía no tiene mucho sentido, por lo que es casi seguro que es el resultado de un error de programador.

Las afirmaciones (Assertions) son una manera de asegurarse de que los errores causan fallas en el punto del error, en lugar de producir silenciosamente valores sin sentido que pueden causar problemas en una parte no relacionada del sistema.

Resumén

Los errores y malas aportaciones son hechos de la vida. Los errores en los programas necesitan ser encontrados y arreglados. Pueden ser más fáciles de notar al tener suites de pruebas (test suites) automatizadas y añadir aserciones a sus programas.

Los problemas causados por factores ajenos al control del programa normalmente deben manejarse con gracia. A veces, cuando el problema se puede manejar localmente, los valores de retorno especiales son una forma sana de rastrearlos. De lo contrario, las excepciones son preferibles.

El lanzamiento de una excepción provoca que la pila de llamadas se desenrolle hasta el siguiente bloque de try/catch o hasta la parte inferior de la pila. El valor de excepción se dará al bloque de catch que lo captura, lo que debería comprobar que es realmente el tipo esperado de excepción y luego hacer algo con él. Para manejar el flujo de control impredecible causado por las excepciones, finalmente los bloques se pueden usar para asegurar que un pedazo de código siempre se ejecuta al finalizar un bloque.

Ejercicios

Retry

Digamos que tiene una función primitiveMultiply que, en el 50 por ciento de los casos, multiplica dos números, y en el otro 50 por ciento, plantea una excepción del tipo MultiplicatorUnitFailure. Escribir una función que envuelva esta mala función y sigue intentandolo hasta que una llamada tenga éxito, después de lo cual devuelva el resultado.

Asegúrese de manejar solo las excepciones que desea manejar.

function MultiplicatorUnitFailure() {}

function primitiveMultiply(a, b) {
  if (Math.random() < 0.5)
    return a * b;
  else
    throw new MultiplicatorUnitFailure();
}

function reliableMultiply(a, b) {
  // Your code here.
}

console.log(reliableMultiply(8, 8));
// → 64

La llamada a primitiveMultiply obviamente ocurre en un bloque try. El bloque catch correspondiente debe volver a crear la excepción cuando no es una instancia de MultiplicatorUnitFailure y asegurarse de que la llamada se reintente cuando si lo es.

Para hacer el reintento, puede utilizar un bucle que se rompe sólo cuando una llamada tiene éxito -como en el ejemplo de vista anterior en este capítulo- o recursión de uso (use recursion) y espero que no obtenga una cadena de errores tan larga que se desborda la pila (Lo qué seguro ocurrirá).

La caja cerrada

Considere el siguiente (y bastante artificial) objeto: object:

var box = {
  locked: true,
  unlock: function() { this.locked = false; },
  lock: function() { this.locked = true;  },
  _content: [],
  get content() {
    if (this.locked) throw new Error("Locked!");
    return this.cContent;
  }
};

It is a box Es una caja con una cerradura. Dentro hay una matriz, pero sólo se puede obtener cuando se desbloquea la caja. No se permite acceder directamente a la propiedad cContent.

Escriba una función llamada WithBoxUnlocked que toma un valor de función como argumento, desbloquea la casilla, ejecuta la función y, a continuación, asegura que la casilla se bloquea de nuevo antes de volver, independientemente de si la función de argumento ha devuelto normalmente o ha lanzado una excepción.

function withBoxUnlocked(body) {
  // Your code here.
}

withBoxUnlocked(function() {
  box.content.push("gold piece");
});

try {
  withBoxUnlocked(function() {
    throw new Error("Pirates on the horizon! Abort!");
  });
} catch (e) {
  console.log("Error raised:", e);
}
console.log(box.locked);
// → true

Para obtener puntos adicionales, asegúrese de que si llama a WithBoxUnlocked cuando la caja ya está desbloqueada, la caja permanezca desbloqueada.

Este ejercicio requiere un bloque finally, como usted probablemente adivinó. Su función primero debe desbloquear la caja y luego llamar a la función de argumento desde dentro de un try. El bloque finally después debe bloquear la caja de nuevo.

Para cerciorarse de que no bloqueamos la caja cuando todavía no estaba bloqueada, revisar su bloqueo al inicio de la función y desbloquearla y bloquearla sólo cuando empezó bloqueada.