JavaScript asíncrono

Aprende cómo JavaScript gestiona las tareas que llevan tiempo sin congelar la página. Sigue el funcionamiento del bucle de eventos para predecir el orden de ejecución del código, programa tareas con temporizadores y escribe código asíncrono en sus tres estilos: funciones de devolución de llamada, promesas y funciones asíncronas con await. Maneja los fallos con catch, try y finally, y ejecuta tareas independientes al mismo tiempo con Promise.all y los demás combinadores de promesas.

PrincipianteUnidad 7 de 9 del curso Curso de Programación en JavaScript

Lo que aprenderás

  1. Distingue el código síncrono del asíncrono, sigue el funcionamiento del bucle de eventos a través de la pila de llamadas y sus colas, y programa tareas con temporizadores.
    • Distingue el código síncrono del asíncrono
      Explica por qué JavaScript ejecuta una parte de tu código a la vez, por qué el código de larga duración congela una página y cómo las operaciones asíncronas permiten que un programa siga trabajando mientras espera.
    • Sigue el funcionamiento del bucle de eventos
      Sigue cómo la pila de llamadas, la cola de tareas y la cola de microtareas determinan el orden de ejecución del código, y predice la salida de código que mezcla temporizadores y promesas.
    • Programa la ejecución de código con temporizadores
      Ejecuta código una vez después de un retraso o repetidamente con setTimeout y setInterval, cancela temporizadores y explica por qué el retraso de un temporizador es una espera mínima y no un tiempo exacto.
  2. Pasa funciones a operaciones asíncronas, comunica errores con la convención del error como primer argumento y reconoce por qué las funciones de devolución de llamada muy anidadas son difíciles de leer y mantener.
    • Pasa funciones de devolución de llamada a operaciones asíncronas
      Escribe funciones que acepten una función de devolución de llamada, distingue las síncronas de las asíncronas y lleva un resultado asíncrono al código que lo necesita.
    • Maneja errores en funciones de devolución de llamada y evita su anidamiento excesivo
      Usa la convención de devolución de llamada con el error como primer argumento, explica por qué try y catch no pueden capturar un error lanzado más tarde en una función de devolución de llamada y reduce el anidamiento excesivo de estas funciones.
  3. Crea promesas, responde a sus resultados con then, catch y finally, y encadena pasos asíncronos en una secuencia legible.
    • Explica los estados de las promesas y crea una promesa
      Describe los estados en los que puede estar una promesa, crea promesas con el constructor Promise y envuelve un temporizador en una promesa.
    • Responde a los resultados de las promesas con then, catch y finally
      Registra manejadores que se ejecuten cuando una promesa se cumpla, se rechace o quede establecida, y úsalos para actualizar una página en el momento adecuado.
    • Encadena promesas en una secuencia
      Devuelve valores y promesas desde los manejadores para construir cadenas sin anidamiento de pasos dependientes, y evita los errores de omisión de return y de anidamiento.
  4. Escribe funciones asíncronas, páusalas con await hasta que las promesas queden establecidas, reescribe cadenas de promesas y API basadas en funciones de devolución de llamada con este estilo, y evita los errores habituales del código asíncrono.
    • Escribe funciones asíncronas y usa await
      Declara funciones asíncronas, explica qué devuelven y usa await para pausar una función hasta que una promesa quede establecida sin bloquear la página.
    • Reescribe cadenas de promesas y API basadas en funciones de devolución de llamada con async y await
      Convierte cadenas de promesas en funciones asíncronas, envuelve funciones basadas en funciones de devolución de llamada en promesas para poder esperarlas y elige un estilo claro.
    • Evita errores habituales con async y await
      Reconoce y corrige las omisiones de await, las funciones de devolución de llamada asíncronas en forEach, las promesas usadas como condiciones y las llamadas asíncronas cuyos errores no se manejan.
  5. Captura fallos y recupérate de ellos en cadenas de promesas y con try y catch, realiza la limpieza con finally y asegúrate de que los fallos nunca pasen desapercibidos.
    • Maneja rechazos en cadenas de promesas
      Sigue cómo un rechazo recorre una cadena de promesas, recupérate de él o transmítelo, y coloca los manejadores catch donde gestionen los fallos adecuados.
    • Usa try, catch y finally con await
      Usa objetos Error y la sentencia try, catch y finally para manejar fallos de promesas esperadas, realizar la limpieza de forma fiable y ofrecer alternativas de respaldo útiles.
    • Mantén visibles los fallos y recupérate de forma controlada
      Evita los rechazos no manejados y los errores silenciados, añade contexto a los errores y elige entre reintentar, usar una alternativa de respaldo e informar al usuario.
  6. Inicia tareas independientes al mismo tiempo, espéralas juntas y elige el combinador de promesas adecuado para situaciones de todo o nada, de mejores resultados posibles, de primera finalización y de primer éxito.
    • Inicia tareas juntas y espera con Promise.all
      Ejecuta tareas asíncronas independientes al mismo tiempo, combina sus resultados con Promise.all y explica su comportamiento en cuanto a tiempos y fallos.
    • Elige entre Promise.allSettled, Promise.race y Promise.any
      Usa allSettled cuando importe cada resultado, race para la primera tarea que quede establecida, como en un tiempo límite, y any para el primer éxito, y compara los cuatro combinadores.

Guía de estudio de JavaScript asíncrono

6 capítulos

1

Explica cómo ejecuta JavaScript el código asíncrono

JavaScript suele tener que esperar mientras una persona hace clic o un temporizador cuenta el tiempo. Durante esa espera, la página debe seguir respondiendo. Hazte una idea de qué se ejecuta ahora, qué espera en el entorno y qué entra en una cola para más tarde. Después, usa esa idea para seguir la salida de la consola y programar temporizadores. La clave es fijarse en cuándo se llama a una función, no solo en dónde aparece su nombre en el código.

1.1 Distingue el código síncrono del asíncrono

El código síncrono termina la sentencia actual antes de pasar a la siguiente. El código JavaScript de tu página se ejecuta en un único hilo principal, por lo que dos partes de ese código no se ejecutan en el mismo instante. Esto es fácil de seguir hasta que una sentencia inicia un trabajo cuyo resultado llegará más tarde.

Empieza ahora y maneja el resultado después

Una operación asíncrona comienza ahora y entrega su resultado después, mediante una función de devolución de llamada o una promesa. El navegador o Node.js es el entorno anfitrión. Gestiona la espera mientras tu programa continúa. Los temporizadores, las acciones del usuario y la carga de imágenes usan este tipo de organización.

console.log("Opening");
setTimeout(() => console.log("Ready"), 700);
console.log("Waiting");

Lee la llamada a setTimeout como «registra esta función». Retorna sin esperar 700 ms. Por tanto, el script actual registra primero «Opening» y «Waiting». Después de unos 700 ms, la función de devolución de llamada puede ejecutarse y registrar «Ready». El retraso exacto también depende de cuándo quede libre el hilo principal.

Aquí hay dos actividades distintas: el entorno controla el temporizador y JavaScript acaba llamando a la función que registra el resultado. La espera se solapa con otro trabajo; la función de devolución de llamada sigue ejecutándose en el hilo principal.

Por qué se congela una página

Un bucle síncrono largo mantiene ocupado el hilo. Mientras se ejecuta, los manejadores de clic y las funciones de devolución de llamada de temporizadores no pueden ejecutarse, y la página no puede actualizar su presentación mediante ese hilo. Registrar un temporizador con retraso cero no le permite interrumpir el bucle.

Supón que un temporizador queda listo mientras un cálculo sigue ejecutándose. El entorno coloca su función de devolución de llamada en una cola. Terminar el cálculo es lo que libera el hilo; el vencimiento del temporizador por sí solo no puede hacerlo. Una función de devolución de llamada que realiza otro cálculo largo también puede congelar la página.

Para reducir el efecto de un cálculo sobre la capacidad de respuesta, divídelo en partes más pequeñas que permitan otras tareas entre ellas, o traslada el trabajo adecuado a un web worker, un hilo de fondo independiente proporcionado por el navegador. Simplemente poner un bucle grande dentro de una función asíncrona deja ese bucle en el hilo principal.

Tres formas de recibir un resultado

Una función de devolución de llamada pasa la siguiente acción como función. Una promesa representa un resultado futuro y permite asociar manejadores de éxito y de fallo. Una función asíncrona trabaja con promesas mientras await permite que su propia ejecución se pause en un punto claro.

Estos estilos coexisten. Un temporizador sigue recibiendo una función de devolución de llamada incluso cuando el código que lo rodea usa promesas. Una función asíncrona puede esperar una promesa producida por una función envolvente de devolución de llamada. Elige el estilo que facilite seguir el recorrido del resultado.

Al leer código asíncrono, marca primero las sentencias que se ejecutan durante la llamada actual. Después marca las funciones registradas para más tarde. Una línea dentro de una función de devolución de llamada registrada no se ejecuta solo porque la ejecución haya llegado a la línea que crea esa función.

Puntos clave

  • JavaScript síncrono ocupa el hilo principal hasta que termina el trabajo actual.
  • El entorno anfitrión gestiona la espera y programa el código que maneja los resultados.
  • Las funciones de devolución de llamada asíncronas se ejecutan más tarde en el hilo principal.
  • Una función asíncrona puede seguir bloqueando la página con un cálculo largo.

1.2 Sigue el funcionamiento del bucle de eventos

El bucle de eventos coordina el trabajo listo para ejecutarse. Para predecir la salida, mantén separadas tres cosas: las funciones que se ejecutan ahora, las funciones de devolución de llamada que esperan como tareas y el trabajo relacionado con promesas que espera como microtareas.

Sigue la pila y las colas

La pila de llamadas contiene las llamadas a funciones activas. Al llamar a una función se añade un marco; al retornar se elimina ese marco. La función actual se ejecuta hasta que retorna o llama a otra función.

Una tarea puede ser un script, una función de devolución de llamada de un temporizador o un manejador de clic. Las tareas se ejecutan una a una. Los navegadores pueden tener varias colas de tareas, así que no supongas que todas las fuentes de trabajo comparten una única cola universal en la que el primero en entrar es el primero en salir.

La cola de microtareas contiene manejadores de promesas de then, catch y finally, continuaciones después de await y funciones de devolución de llamada pasadas a queueMicrotask. En un punto de comprobación de microtareas posterior al trabajo actual, la cola se vacía antes de la siguiente tarea. Las microtareas añadidas durante ese vaciado también se ejecutan antes de la siguiente tarea.

Sigue un script
console.log("begin");
setTimeout(() => console.log("timer"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("finish");
  1. El script registra «begin».
  2. Registra una función de devolución de llamada de un temporizador; no la llama.
  3. Coloca el manejador de la promesa cumplida en la cola como microtarea.
  4. Registra «finish» y termina el trabajo actual.
  5. La microtarea registra «promise».
  6. La tarea del temporizador registra «timer».

Una promesa ya cumplida sigue programando su manejador para más tarde. Un retraso de 0 ms sigue programando una tarea de temporizador.

Incluye los trabajos añadidos durante el proceso
Promise.resolve()
  .then(() => console.log("first"))
  .then(() => console.log("third"));
queueMicrotask(() => console.log("second"));
console.log("now");

El script registra «now». La cola de microtareas contiene inicialmente el primer manejador de promesa y luego la función de devolución de llamada de queueMicrotask. Cuando el primer manejador registra «first» y retorna, deja listo el siguiente manejador de promesa. Ese nuevo manejador se incorpora al final de la cola. La salida restante es «second» y luego «third».

Finalización y capacidad de respuesta

Una vez que comienza una tarea o microtarea, el trabajo puesto en cola de forma asíncrona no puede interrumpirla. Este comportamiento de ejecución hasta completarse te permite razonar sobre un cambio local sin que un temporizador entre de repente a mitad de la función.

Después de que terminen las microtareas, el navegador puede actualizar la pantalla en una oportunidad de renderizado. No tiene que volver a dibujarla después de cada tarea. Una sucesión interminable de microtareas puede impedir tanto las tareas posteriores como el renderizado: cada nueva microtarea mantiene en curso el vaciado actual. Por tanto, aplazar trabajo a una microtarea no garantiza que la página tenga oportunidad de responder.

Para hacer un seguimiento, escribe primero la salida inmediata, vacía las microtareas en orden y solo entonces pasa a las tareas de temporizador que estén listas. Añade el trabajo recién programado a la cola adecuada cuando el código realmente lo programe.

Sigue un temporizador creado por una microtarea
setTimeout(() => console.log("outer timer"), 0);
Promise.resolve().then(() => {
  console.log("job one");
  setTimeout(() => console.log("inner timer"), 0);
}).then(() => console.log("job two"));
console.log("script");

El script registra el temporizador externo y el primer manejador de promesa, y luego registra «script». La primera microtarea registra «job one» y registra el temporizador interno. Al retornar de ese manejador se coloca «job two» en la cola, que también se ejecuta antes de una tarea de temporizador. La salida termina con «outer timer» y luego «inner timer»: ambos temporizadores tienen el mismo retraso y el externo se registró primero.

Crear un temporizador durante una microtarea no convierte su función de devolución de llamada en una microtarea. Clasifica el trabajo según la API de programación que lo recibe y después añádelo a la cola adecuada.

Puntos clave

  • La pila de llamadas registra las llamadas activas.
  • Los manejadores de promesas y las continuaciones de await se ejecutan como microtareas.
  • Vacía las microtareas recién añadidas antes de tomar otra tarea.
  • Las funciones de devolución de llamada en cola no pueden interrumpir el JavaScript que se está ejecutando.
  • El renderizado es posible después de las microtareas, pero no está garantizado después de cada tarea.

1.3 Programa la ejecución de código con temporizadores

Los temporizadores programan funciones de devolución de llamada para más tarde. Miden un retraso en milisegundos: 1000 ms son 1 s. El retraso determina cuándo una función de devolución de llamada puede quedar lista para ejecutarse; otros trabajos y los límites del navegador pueden hacer que se ejecute más tarde.

Programa y cancela una llamada
function remind(topic) {
  console.log("Review " + topic);
}
const reminder = setTimeout(remind, 2000, "promises");
// To cancel before it runs:
clearTimeout(reminder);

Los argumentos adicionales después del retraso se pasan a la función de devolución de llamada. Si se omite el retraso, se considera 0. Guarda el identificador de temporizador devuelto si podrías necesitar cancelarlo. Los navegadores devuelven un identificador numérico; Node.js devuelve un objeto Timeout. Ambos funcionan con clearTimeout. Cancelar un temporizador ya terminado o inexistente no tiene efecto.

Pasa una función: setTimeout(remind, 2000, "promises"). Escribir setTimeout(remind("promises"), 2000) llama a remind inmediatamente y pasa su valor de retorno al temporizador. Una función flecha es otra forma de retrasar una llamada con argumentos: setTimeout(() => remind("promises"), 2000).

Repite con un temporizador periódico u otro de ejecución única
let ticks = 0;
const clock = setInterval(() => {
  ticks++;
  console.log("Tick " + ticks);
  if (ticks === 3) clearInterval(clock);
}, 1000);

La primera marca ocurre después de aproximadamente un segundo. La tercera función de devolución de llamada detiene las marcas futuras. Conserva el identificador del temporizador periódico y usa clearInterval cuando deba terminar el trabajo repetido.

Una función de devolución de llamada puede, en cambio, programar su siguiente temporizador de ejecución única. Cuando llama a setTimeout al final de su trabajo síncrono, el siguiente retraso comienza después de ese trabajo. Esto da una pausa entre ejecuciones y permite un retraso diferente cada vez. Un temporizador periódico sigue solicitando ejecuciones según un ritmo fijo; el trabajo lento puede dejar poca pausa. Si una ejecución inicia trabajo asíncrono, espera también a que termine antes de programar la siguiente ejecución cuando el solapamiento sea un problema.

Los tiempos tienen límites

Un hilo ocupado retrasa las funciones de devolución de llamada que están listas. Los navegadores también ralentizan los temporizadores en pestañas en segundo plano, a menudo hasta aproximadamente una comprobación por segundo o menos. Cuando el nivel de anidamiento de temporizadores es superior a cinco, la regla de temporizadores de HTML eleva los retrasos inferiores a 4 ms hasta 4 ms. Los retrasos muy grandes también requieren atención: el límite habitual de enteros con signo de 32 bits es 2147483647 ms, unos 24,8 días; los valores superiores pueden causar un desbordamiento y activarse mucho antes.

Captura el valor correcto del bucle
for (let i = 1; i <= 3; i++) {
  setTimeout(() => console.log(i), 100);
}

Cada iteración con let tiene su propio enlace, por lo que la salida es «1», «2», «3». Con var, todas las funciones de devolución de llamada comparten la misma variable. Después del bucle vale 4, por lo que cada una registra «4».

Espera un periodo de inactividad

La agrupación de llamadas tras un periodo de inactividad cancela un temporizador de ejecución única pendiente cada vez que llega un nuevo evento. Un nuevo temporizador programa entonces la acción después del evento más reciente. Con un retraso de 300 ms, escribir cada 100 ms sigue posponiendo la acción; esta se ejecuta unos 300 ms después de que se deja de escribir. Guarda el identificador fuera del manejador para que la siguiente llamada pueda cancelar el temporizador anterior.

Construye un manejador para agrupar llamadas
let pendingSave;
function scheduleSave(text) {
  clearTimeout(pendingSave);
  pendingSave = setTimeout(() => {
    console.log("Saved: " + text);
  }, 300);
}
scheduleSave("n");
scheduleSave("no");
scheduleSave("note");

Estas llamadas ocurren en el mismo script. Cada una cancela el identificador anterior antes de sustituirlo, por lo que solo se registra «Saved: note», después de unos 300 ms. La función de devolución de llamada final captura el parámetro de texto de esa llamada. El identificador vive fuera de la función para que cada llamada pueda cancelar el temporizador programado anteriormente.

Si el identificador se declarara de nuevo dentro de cada llamada, la siguiente llamada no conservaría el identificador del temporizador anterior. Si un manejador programara sin cancelar, entregaría los tres mensajes. La agrupación de llamadas necesita tanto el identificador compartido como la cancelación antes de sustituirlo.

Puntos clave

  • Pasa una función de devolución de llamada y mide los retrasos en milisegundos.
  • Usa el identificador devuelto para cancelar un temporizador.
  • Los retrasos de los temporizadores no garantizan tiempos de ejecución exactos.
  • Un temporizador periódico se repite hasta que se cancela.
  • Usa let para obtener enlaces de bucle separados.
  • La agrupación de llamadas reinicia un retraso después de cada evento.
2

Escribe y gestiona código basado en funciones de devolución de llamada

Una función de devolución de llamada permite a quien llama proporcionar la siguiente parte del trabajo como función. Esa sencilla idea sirve de base a los temporizadores, los eventos y muchas API antiguas. Aquí escribirás una pequeña operación basada en una función de devolución de llamada, seguirás el resultado hasta el código que lo necesita y transmitirás los fallos mediante un parámetro de error. También verás cómo los pasos dependientes crean código anidado y por qué importa un manejo de errores coherente en cada nivel.

2.1 Pasa funciones de devolución de llamada a operaciones asíncronas

Una función de devolución de llamada es una función que pasas a otra función para que esta la llame. El código de la función de devolución de llamada describe qué debe ocurrir con un resultado. Que se ejecute durante la llamada actual o más tarde depende de la API que la recibe.

Una función de devolución de llamada puede ejecutarse de forma síncrona
const doubled = [2, 5].map(n => n * 2);
console.log(doubled);

map llama a su función de devolución de llamada para cada elemento antes de devolver el nuevo array. El resultado es [4, 10]. La función flecha es una función de devolución de llamada, pero este código no programa trabajo futuro. Puedes usar doubled en la siguiente línea porque el array ya está completo.

Entrega un resultado diferido
function prepareLabel(name, callback) {
  setTimeout(() => {
    callback("Label: " + name);
  }, 200);
}
prepareLabel("Mira", label => {
  console.log(label);
});
console.log("Label requested");

prepareLabel inicia un temporizador y retorna. El script registra «Label requested». Después de unos 200 ms, la función de devolución de llamada del temporizador construye la etiqueta y llama a la función proporcionada, que registra «Label: Mira». La operación elige cuándo llamar; quien llama elige qué hacer con el resultado.

Pon el trabajo dependiente donde existe el resultado
let label;
prepareLabel("Mira", value => {
  label = value;
});
console.log(label);

Esto registra undefined: la asignación aún no ha ocurrido. Traslada console.log(value) a la función de devolución de llamada. Añadir un temporizador más largo y sin relación antes de leer label solo supondría adivinar cuándo estará disponible el resultado; usar la función de devolución de llamada del resultado vincula el trabajo con la finalización real.

Un return dentro de una función de devolución de llamada diferida tampoco puede devolver el valor desde la función externa. La llamada externa ya ha terminado. En el ejemplo, return "Label: Mira" dentro de la función del temporizador retornaría al mecanismo del temporizador, dejando a quien llamó a prepareLabel sin la etiqueta diferida.

Pasa la siguiente acción sin llamarla

Una función de devolución de llamada con nombre también funciona:

function showLabel(label) {
  console.log(label);
}
prepareLabel("Mira", showLabel);

showLabel es el valor de la función. showLabel() la llamaría inmediatamente sin ninguna etiqueta y pasaría su valor de retorno en su lugar. Usa una función envolvente de tipo flecha cuando la acción posterior necesite un argumento concreto o varias sentencias.

Para dos pasos asíncronos dependientes, inicia el segundo dentro de la función de devolución de llamada del primer paso. Por ejemplo, después de recibir un usuario, pasa ese usuario a una operación que prepare su configuración. Iniciar ambos fuera de sus funciones de devolución de llamada no establecería esa dependencia.

La función que recibe la función de devolución de llamada controla con qué frecuencia la llama. Algunas, como los manejadores de eventos, se llaman muchas veces de forma intencionada. Una operación con un solo resultado debe llamar una vez a su función de devolución de llamada del resultado. Una promesa da a esa operación un resultado que solo puede establecerse una vez.

Puntos clave

  • El momento de ejecución de una función de devolución de llamada depende de la API que la llama.
  • map usa funciones de devolución de llamada síncronas; la función de un temporizador se ejecuta más tarde.
  • Usa un resultado diferido dentro de su función de devolución de llamada o del código llamado desde ella.
  • Retornar de una función de devolución de llamada no implica retornar de su función externa.

2.2 Maneja errores en funciones de devolución de llamada y evita su anidamiento excesivo

Una operación asíncrona puede fallar o tener éxito. Una API basada en funciones de devolución de llamada necesita una forma clara de comunicar cada resultado y un lugar claro para que quien llama lo maneje.

Comprueba primero el error

La convención del error como primer argumento usa callback(err, result). Una llamada exitosa pasa null como error. Una llamada fallida pasa un objeto Error y no proporciona un resultado utilizable.

function readLabel(id, callback) {
  setTimeout(() => {
    if (id <= 0) {
      callback(new Error("Invalid label id"));
      return;
    }
    callback(null, { id: id, text: "Draft" });
  }, 200);
}
readLabel(4, (err, label) => {
  if (err) {
    console.log(err.message);
    return;
  }
  console.log(label.text);
});

La función que produce el resultado se detiene después de comunicar un identificador no válido, por lo que no puede comunicar también un éxito. La función que consume el resultado se detiene al detectar un error, por lo que no puede intentar leer label.text de un resultado ausente. Con el identificador 4, la salida es «Draft». Con el identificador 0, es «Invalid label id».

Ambos retornos importan. Sin el retorno de la función consumidora, un segundo error sobre un valor indefinido podría ocultar el útil mensaje original. Sin el retorno de la función productora, una operación podría llamar dos veces a la función de devolución de llamada. Para una API con un solo resultado, comunica exactamente un resultado.

Un try externo ya ha terminado
try {
  setTimeout(() => {
    throw new Error("Late failure");
  }, 0);
} catch (err) {
  console.log("Handled");
}

El try programa el temporizador y termina. La función de devolución de llamada lanza el error en una tarea posterior, cuando ese try ya no está activo, por lo que no se registra «Handled». Captura un posible error lanzado dentro de la función de devolución de llamada o transmite el fallo mediante el parámetro de error de la API. Una función envolvente basada en promesas posterior debe convertir explícitamente ese error de la función de devolución de llamada en un rechazo.

Mantén legibles los pasos dependientes

El código de funciones de devolución de llamada se vuelve difícil de seguir cuando cada paso siguiente aparece dentro de la función de devolución de llamada del paso anterior. Leer un perfil, después sus proyectos y luego las tareas de un proyecto crea varios niveles de anidamiento. Cada operación también necesita su propia comprobación de errores. Este patrón suele llamarse infierno de funciones de devolución de llamada o pirámide de la perdición.

Las funciones con nombre pueden dar nombres significativos a esos pasos y reducir el anidamiento visual. Mantén coherente la comunicación de errores entre ellas: un paso posterior puede fallar igual que el primero. Las promesas y las funciones asíncronas ofrecen una forma menos anidada de expresar la misma secuencia manteniendo conectada la propagación de fallos.

No todas las API basadas en funciones de devolución de llamada usan un parámetro de error. Algunos objetos del navegador tienen eventos separados para el éxito y el fallo. Para una imagen, establece los manejadores onload y onerror antes de asignar src para que ambos resultados tengan un manejador cuando comience la carga. Lee el contrato de la API antes de suponer que existe un parámetro llamado err.

Puntos clave

  • Una función de devolución de llamada con el error como primer argumento recibe err antes del resultado.
  • Retorna después de comunicar o manejar un error.
  • Las operaciones con un solo resultado deben llamar a su función de devolución de llamada exactamente una vez.
  • Un try externo no puede capturar un error lanzado más tarde por una función de devolución de llamada.
  • Los pasos con nombre o el código basado en promesas reducen el anidamiento profundo.
3

Crea y usa promesas

Una promesa proporciona a una operación un objeto compartido para su resultado futuro. Hace explícitos el éxito y el fallo y permite conectar pasos dependientes sin anidar cada uno dentro del anterior. Empieza construyendo una promesa alrededor de un temporizador. Después, asocia manejadores, sigue las nuevas promesas que devuelven y comprueba que cada paso asíncrono permanezca conectado a la cadena que depende de él.

3.1 Explica los estados de las promesas y crea una promesa

Una promesa representa un resultado futuro. Una función puede devolverla inmediatamente, lo que permite a quien llama conectar trabajo con un resultado que todavía no existe.

Estados y establecimiento

Una promesa está pendiente hasta que se cumple con un valor o se rechaza con un motivo. Las promesas cumplidas y rechazadas están establecidas; su resultado no puede cambiar.

«Resuelta» es más preciso que un sinónimo cotidiano de «exitosa». Una promesa resuelta con otra promesa pendiente sigue el resultado final de esa promesa, incluido el rechazo. Por tanto, puede estar resuelta y seguir pendiente.

Crea una promesa alrededor de una operación
function wait(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}
wait(200).then(() => console.log("Pause finished"));

new Promise llama inmediatamente a su función ejecutora. Esa función registra un temporizador. Cuando el temporizador llama a resolve, la promesa se cumple con undefined y el manejador then registrado se ejecuta más tarde como microtarea. Esta función auxiliar proporciona una pausa reutilizable sin bloquear el hilo.

Una función ejecutora puede recibir tanto resolve como reject:

function reserve(quantity) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (quantity <= 8) resolve(8 - quantity);
      else reject(new Error("Not enough stock"));
    }, 200);
  });
}
reserve(3).then(left => console.log(left));
reserve(9).catch(err => console.log(err.message));

Para estas cantidades positivas, la primera llamada se cumple con 5 y la segunda se rechaza con «Not enough stock». La promesa permite a quien llama recibir cualquiera de los dos resultados mediante el mismo objeto devuelto.

La creación inicia la función ejecutora
console.log("before");
const result = new Promise(resolve => {
  console.log("creating");
  resolve(12);
});
console.log("after");
result.then(value => console.log(value));

La salida es «before», «creating», «after», «12». Asociar then no inicia esta función ejecutora. El constructor ya la ejecutó; then registra el código que consume su resultado.

Solo la primera llamada a resolve o reject decide el destino de la promesa. Las llamadas posteriores no pueden sustituirlo. Esto también se aplica cuando la primera resolución adopta otra promesa cuyo resultado sigue pendiente. Llamar a resolve no detiene el código normal de la función ejecutora, así que usa return cuando una rama deba terminar.

Haz que cada ruta termine correctamente

Un error lanzado de forma síncrona en la función ejecutora rechaza la promesa automáticamente. Un error lanzado en una función de devolución de llamada de un temporizador posterior está fuera de esa llamada y no se convierte en un rechazo. Usa reject(new Error("...")) allí; de lo contrario, el error puede quedar sin capturar mientras la promesa permanece pendiente.

Una rama que no llama a ninguna de las dos funciones puede dejar a quien llama esperando para siempre. Usa objetos Error como motivos de rechazo para que los manejadores puedan leer un mensaje y las herramientas de depuración puedan mostrar información útil sobre el error.

Promise.resolve(value) crea una promesa cumplida para un valor normal o adopta el resultado de otra promesa. Promise.reject(error) crea una promesa rechazada. Añade manejo del rechazo cuando uses esta última.

Sigue un resultado adoptado
const inner = new Promise(resolve => {
  setTimeout(() => resolve("packed"), 250);
});
const outer = new Promise((resolve, reject) => {
  resolve(inner);
  reject(new Error("Too late to replace it"));
});
outer.then(value => console.log(value));

La función ejecutora de inner inicia su temporizador durante la construcción. Después, la función ejecutora de outer resuelve con inner, vinculando outer al resultado final de inner. La llamada posterior a reject no puede sustituir esa decisión. Outer permanece pendiente hasta que inner se cumple, y su manejador registra «packed» después de unos 250 ms.

Si inner se rechazara en su lugar, outer seguiría ese rechazo. La primera llamada a resolve seleccionó qué resultado seguir; no garantizó el éxito. Esta distinción importa cuando una función envolvente transmite otra promesa en lugar de proporcionar un valor normal ya disponible.

Puntos clave

  • Las promesas pendientes se cumplen o se rechazan; los resultados establecidos permanecen fijos.
  • La resolución puede adoptar el resultado final de otra promesa.
  • La función ejecutora se ejecuta de forma síncrona durante la construcción.
  • Usa resolve y reject para conectar las funciones de devolución de llamada posteriores con la promesa.
  • Un error lanzado por una función de devolución de llamada posterior no rechaza automáticamente la promesa que la contiene.

3.2 Responde a los resultados de las promesas con then, catch y finally

Los manejadores de promesas conectan código con el éxito, el fallo o cualquiera de los dos resultados. Cada registro de un manejador también devuelve una nueva promesa, lo que permite que la siguiente parte del código reciba lo que ese manejador produzca.

Usa el manejador adecuado para cada resultado

p.then(onFulfilled) recibe un valor de cumplimiento. p.catch(onRejected) recibe un motivo de rechazo; es una forma abreviada de p.then(undefined, onRejected). Los manejadores son funciones y se ejecutan como microtareas, aunque p ya esté establecida.

Promise.resolve("draft")
  .then(text => console.log(text));
console.log("registered");

El script registra «registered» antes de que el manejador registre «draft». Un valor disponible de inmediato no hace que el manejador sea síncrono.

Es fundamental pasar una función. then(console.log("draft")) registra «draft» durante el registro y pasa undefined como manejador. Usa then(() => console.log("draft")) cuando el mensaje deba registrarse más tarde. then(console.log) pasa la propia función de registro, por lo que recibe el valor.

Realiza la limpieza después del éxito o del fallo

finally es útil para ocultar un indicador de carga o habilitar un botón después de que una operación quede establecida. Su función de devolución de llamada no recibe un valor ni un error como argumento.

Promise.resolve(4)
  .finally(() => console.log("Cleanup"))
  .then(value => console.log(value));

La salida es «Cleanup» y luego «4». Devolver un valor normal desde la función de limpieza no sustituye 4. Una promesa devuelta puede retrasar el siguiente paso hasta que termine la limpieza. Si la limpieza lanza un error o devuelve una promesa rechazada, ese fallo se convierte en el nuevo resultado de la cadena.

Un flujo habitual de una página establece un estado de carga antes de empezar, maneja un resultado en then, comunica un fallo en catch y restablece el estado de carga en finally. Pon el trabajo que depende del resultado en then o catch, donde existe el argumento necesario.

Distingue las suscripciones de una cadena
const p = Promise.resolve(4);
p.then(value => value * 2);
p.then(value => console.log(value));

Estas son dos suscripciones separadas a p. Ambas reciben 4. La multiplicación pertenece a la nueva promesa devuelta por el primer then; no modifica p ni pasa su resultado a la segunda suscripción.

Conéctalas para pasar el valor transformado:

p.then(value => value * 2)
  .then(value => console.log(value));

Ahora el manejador final recibe 8. La diferencia está en la promesa a la que se asocia cada manejador.

El objeto devuelto sigue siendo una promesa

En const next = p.then(value => value * 2), next es una nueva promesa, no el número 8. El manejador puede ejecutarse después de que termine el script que lo rodea y puede devolver a su vez trabajo diferido. Usa otro manejador o await para obtener el valor. Tratar next inmediatamente como un número hace perder esta información sobre los tiempos.

Puntos clave

  • Usa then para valores, catch para rechazos y finally para la limpieza.
  • Pasa funciones como manejadores.
  • Cada then devuelve una nueva promesa.
  • Los manejadores separados de una promesa reciben su resultado original.
  • Una limpieza exitosa en finally conserva el resultado anterior; una limpieza fallida lo sustituye.

3.3 Encadena promesas en una secuencia

Una cadena de promesas convierte la salida de un manejador en la entrada del siguiente. Cada eslabón debe devolver el valor o la promesa que necesitan los eslabones posteriores.

Pasa valores por la cadena
Promise.resolve(3)
  .then(count => count * 4)
  .then(total => total + 2)
  .then(total => console.log(total));

El primer manejador recibe 3 y devuelve 12. El segundo recibe 12 y devuelve 14. El manejador final registra «14». Una expresión de función flecha devuelve su resultado automáticamente; un cuerpo de función con llaves necesita un return explícito para transmitir un valor.

Devuelve una promesa para un paso dependiente
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
function makeCode() {
  return wait(100).then(() => "P4");
}
function makeLabel(code) {
  return wait(200).then(() => "Parcel " + code);
}
makeCode()
  .then(code => makeLabel(code))
  .then(label => console.log(label));

makeCode proporciona «P4» después de unos 100 ms. El siguiente manejador devuelve la promesa de makeLabel. La cadena sigue esa promesa, espera unos 200 ms más y pasa «Parcel P4» al manejador final. Quien llama recibe una etiqueta, no una promesa anidada dentro del valor.

Encuentra un eslabón roto

Compara ese manejador intermedio con este:

.then(code => {
  makeLabel(code);
})

Inicia makeLabel, pero no devuelve nada. Su nueva promesa se cumple con undefined sin esperar la etiqueta. Por tanto, el siguiente manejador recibe undefined después del paso del código, mientras la preparación de la etiqueta continúa por separado. Los errores de ese trabajo separado también dejan de propagarse por esta cadena externa.

Corrígelo con return makeLabel(code); dentro de las llaves o elimina las llaves para devolver la expresión. La misma regla se aplica cuando un manejador inicia una pausa: .then(() => wait(200)) espera; .then(() => { wait(200); }) no.

Reduce el anidamiento y conserva los valores necesarios

Poner otro then en profundidad dentro del primer manejador puede recrear el anidamiento propio de las funciones de devolución de llamada. Devuelve la promesa interna y coloca el siguiente manejador en el nivel externo. Esto conserva una sola ruta para los tiempos, los valores y los errores.

Cada manejador tiene su propio ámbito local. Si un paso posterior necesita tanto el código como la etiqueta, devuelve un objeto que los contenga:

makeCode()
  .then(code => makeLabel(code).then(label => ({ code, label })))
  .then(pair => console.log(pair.code + ": " + pair.label));

Este pequeño manejador interno construye el valor combinado y se devuelve, por lo que permanece conectado. Una función asíncrona también puede conservar ambos resultados en variables locales normales.

Un rechazo omite los manejadores de cumplimiento posteriores hasta que llega a un manejador de rechazo. Un catch final puede manejar fallos de todos los pasos conectados que lo preceden. El trabajo dependiente pertenece a esta secuencia; el trabajo independiente puede iniciarse conjuntamente con Promise.all.

Puntos clave

  • Devuelve el valor que necesita el siguiente manejador.
  • Devolver una promesa hace que la cadena espere su resultado.
  • Los cuerpos de funciones flecha con llaves necesitan return.
  • Mantén el trabajo dependiente y sus errores conectados a la cadena externa.
  • Transmite los datos combinados cuando los pasos posteriores necesiten valores locales anteriores.
4

Escribe código asíncrono con async y await

Las funciones asíncronas permiten escribir pasos basados en promesas en el orden en que quieres que ocurran. Los valores pueden mantenerse en variables con nombre y await marca los puntos en los que se pausa la función. Aprende cómo afecta esa pausa a quien llama, convierte código antiguo a este estilo y detecta errores que dejan una promesa donde se necesita un valor. El comportamiento de los bucles merece especial atención porque una función de devolución de llamada asíncrona no hace que quien la llama espere.

4.1 Escribe funciones asíncronas y usa await

Una función asíncrona siempre devuelve una promesa. Dentro de ella, await pausa la función hasta que hay un valor disponible, lo que permite leer los pasos dependientes de arriba abajo.

Devuelve un valor mediante una promesa
async function getCount() {
  return 6;
}
getCount().then(count => console.log(count));

Llamar a getCount() da una promesa cumplida con 6. El manejador registra «6». Si la función lanza un error, su promesa devuelta se rechaza. Al devolver otra promesa, la función asíncrona sigue el resultado final de esa promesa.

Puedes declarar una función asíncrona con async function name(), una función flecha asíncrona como const name = async () => {} o un método asíncrono de un objeto. El comportamiento de devolución de una promesa es el mismo.

Pausa en await
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
async function showSteps() {
  console.log("Step one");
  await wait(300);
  console.log("Step two");
}
showSteps();
console.log("Caller continues");

La salida empieza con «Step one» y luego «Caller continues». Unos 300 ms después, registra «Step two». Al llamar a la función, su cuerpo se ejecuta de forma síncrona hasta el primer await. La pausa devuelve el control a quien llamó. Una vez que la promesa se cumple, el resto de showSteps se reanuda mediante la cola de microtareas.

Await pausa su función, no todo el hilo. Otras tareas listas pueden ejecutarse durante la espera. Un cálculo síncrono grande antes o después de await aún puede bloquear el hilo mientras se ejecuta.

Lee un valor o recibe un error

Una promesa cumplida proporciona su valor: const count = await getCount(); asigna 6 a count. Una promesa rechazada lanza su motivo en la línea de await. Rodea la operación esperada con try y catch cuando esta función deba manejar ese fallo.

await también acepta valores normales. En const count = await 6;, el resultado sigue siendo 6, pero la continuación de la función se aplaza. No se vuelve síncrona solo porque no haya sido necesaria una operación de espera.

Comprueba dónde corresponde await

Usa await dentro de la función asíncrona cuyo cuerpo lo contiene. También está permitido en el nivel superior de un módulo de JavaScript. El cuerpo de una función normal no puede contener await; una función asíncrona externa no convierte en asíncrona una función de devolución de llamada normal anidada.

Quien llama sigue recibiendo una promesa. Para usar el valor final, espéralo desde otra función asíncrona o asocia then. Por ejemplo, const total = getCount(); almacena una promesa; no almacena 6.

Un manejador de eventos puede ser asíncrono. El manejador puede esperar un paso de preparación antes de actualizar una página. Da a esa acción su propio manejo de errores porque el despacho del evento no espera la promesa devuelta por el manejador. Al principio de un programa, una llamada como showSteps().catch(console.error) proporciona un manejador para un fallo imprevisto.

Puntos clave

  • Una función asíncrona devuelve una promesa para su valor de retorno o el error que lance.
  • El código anterior al primer await se ejecuta durante la llamada.
  • Await se reanuda más tarde con un valor o lanza un motivo de rechazo.
  • Await corresponde dentro de una función asíncrona o en el nivel superior de un módulo.

4.2 Reescribe cadenas de promesas y API basadas en funciones de devolución de llamada con async y await

Una cadena de promesas puede convertirse en una función asíncrona sin cambiar sus dependencias. Cada resultado recibe una variable local y cada paso esperado comienza solo cuando está listo el resultado anterior que necesita.

Sustituye manejadores por asignaciones con espera
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
function getUser(id) {
  return wait(100).then(() => ({ id, name: "Nia" }));
}
function getOrders(user) {
  return wait(200).then(() => [user.name + "-1", user.name + "-2"]);
}
async function showOrders(id) {
  const user = await getUser(id);
  const orders = await getOrders(user);
  console.log(user.name + ": " + orders.length + " orders");
}
showOrders(8).catch(console.error);

La primera espera proporciona un usuario. Solo entonces puede comenzar getOrders(user). Después de unos 300 ms en total, la función registra «Nia: 2 orders». Tanto user como orders están disponibles en el ámbito de esa línea final. En una cadena, el parámetro de cada manejador pertenecería a un ámbito local separado.

La cadena correspondiente devolvería getOrders(user) desde el primer manejador. Reescribirla con await hace explícita esa espera y mantiene intacto el orden. Un catch final de la cadena se convierte en un try/catch alrededor de los pasos esperados; finally se convierte en un bloque de limpieza.

Convierte una API basada en funciones de devolución de llamada en una API de promesas

Añadir async a una función envolvente no hace que el valor diferido de una API basada en funciones de devolución de llamada se convierta en su valor de retorno. Conecta la función de devolución de llamada con el establecimiento de la promesa:

function readTag(id, callback) {
  setTimeout(() => {
    if (id <= 0) callback(new Error("Invalid tag"));
    else callback(null, "Tag " + id);
  }, 100);
}
function readTagAsync(id) {
  return new Promise((resolve, reject) => {
    readTag(id, (err, tag) => {
      if (err) {
        reject(err);
        return;
      }
      resolve(tag);
    });
  });
}

Dentro de una función asíncrona, const tag = await readTagAsync(8); da «Tag 8». Llamarla con 0 en su lugar lanza «Invalid tag» en la espera. El éxito resuelve la función envolvente; el fallo la rechaza. El adaptador cambia cómo se entrega el resultado, no lo que hace la operación subyacente.

Esta conversión se llama conversión a promesas. Para las API habituales con un solo resultado y el error como primer argumento, Node.js también proporciona util.promisify, y muchos módulos integrados ya tienen variantes basadas en promesas, como fs/promises.

Mantén una ruta clara

Los temporizadores se convierten en pausas mediante una función auxiliar wait que devuelve una promesa. Un bucle asíncrono puede registrar un paso, esperar la pausa y comenzar la siguiente iteración. Con funciones de devolución de llamada, habría que programar el siguiente paso dentro de la función anterior.

Las promesas, las cadenas y las funciones asíncronas funcionan juntas: una función asíncrona puede esperar una cadena y quien llama puede asociar then a la promesa devuelta por una función asíncrona. Dentro de una función, usa la forma que facilite seguir las dependencias y el manejo de errores. Un constructor Promise adicional es útil para adaptar una función de devolución de llamada, pero las operaciones existentes que devuelven promesas pueden esperarse directamente.

Puntos clave

  • Cada paso dependiente de una cadena puede convertirse en una asignación con espera.
  • Las variables locales mantienen disponibles los resultados anteriores más adelante en la función asíncrona.
  • Convierte funciones de devolución de llamada a promesas asociando los errores a reject y los resultados a resolve.
  • Las API de promesas existentes pueden esperarse directamente.

4.3 Evita errores habituales con async y await

El código asíncrono puede parecer síncrono y, aun así, pasar promesas entre funciones. Muchos errores surgen al leer el valor futuro demasiado pronto o al suponer que una función auxiliar espera funciones de devolución de llamada que ignora.

Espera el valor antes de usarlo
async function getQuantity() {
  return 18;
}
async function total() {
  const quantity = await getQuantity();
  console.log(quantity * 3);
}
total().catch(console.error);

Esto registra «54». Sin await, quantity contendría una promesa. Multiplicar esa promesa por 3 produce NaN, no un cálculo diferido que se actualiza automáticamente.

Una condición presenta la misma trampa. Una comprobación asíncrona que devuelve false sigue devolviendo un objeto promesa, y los objetos son verdaderos en contexto booleano. Usa if (await allowed()) dentro de una función asíncrona cuando la rama dependa del booleano final. Evaluar if (allowed()) comprueba el propio objeto.

forEach no recopila las promesas de las funciones de devolución de llamada
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
async function labelAll(ids) {
  ids.forEach(async id => {
    await wait(100);
    console.log("Label " + id);
  });
  console.log("Labels finished");
}
labelAll([4, 9]).catch(console.error);

«Labels finished» aparece antes que las etiquetas. forEach llama a ambas funciones de devolución de llamada, ignora las promesas que devuelven y termina su propia iteración síncrona. Incluso await ids.forEach(...) esperaría undefined, no el trabajo de las funciones de devolución de llamada. Un fallo dentro de esas promesas ignoradas también queda fuera de la promesa devuelta por labelAll.

Para el procesamiento ordenado, escribe un bucle normal:

for (const id of ids) {
  await labelOne(id);
}
console.log("Labels finished");

Aquí labelOne es una operación que devuelve una promesa. Cada elemento comienza después de que termina el anterior. Para elementos independientes, usa await Promise.all(ids.map(id => labelOne(id))); antes del registro final. map recopila las promesas devueltas y Promise.all da a quien llama una sola promesa para su finalización.

Distingue la independencia de la sintaxis

Esperar cada operación inmediatamente hace que sus esperas sean secuenciales. Para esperas independientes de 1000, 2000 y 1500 ms, eso tarda unos 4500 ms. Iniciar las tres y combinarlas tarda unos 2000 ms. Un paso que necesita un resultado anterior sigue teniendo que esperarlo.

Mantén conectados los fallos

Una función anidada necesita su propia palabra clave async para usar await, pero esa palabra clave por sí sola no hace que quien la llama espere el resultado. Comprueba ambos extremos: ¿la función de devolución de llamada devuelve una promesa y quien la llama la usa?

Para trabajo iniciado deliberadamente sin esperar su finalización, asocia un manejador de rechazo como backgroundTask().catch(console.error). Comprueba también los retornos en funciones envolventes asíncronas: async function prepare() { await wait(100); } se cumple con undefined. Esperar un paso y devolver su resultado son decisiones distintas.

Devuelve los resultados del grupo corregido
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
async function prepareItem(id) {
  await wait(50);
  return "Item " + id;
}
async function prepareAll(ids) {
  return await Promise.all(ids.map(id => prepareItem(id)));
}
prepareAll([6, 2]).then(items => console.log(items.join(", ")));

Cada función de devolución de llamada de map devuelve una promesa y map conserva esas promesas en un array. All espera a ambas y proporciona ["Item 6", "Item 2"]. PrepareAll devuelve ese array mediante su propia promesa, por lo que quien llama registra «Item 6, Item 2» después de que termina el grupo.

Quitar el return de prepareAll seguiría esperando al grupo, pero quien llama recibiría undefined. Sustituir map por forEach haría perder el array de promesas. Comprueba por separado la recopilación, la espera y la devolución al corregir una acción con varios elementos.

Puntos clave

  • Espera antes de usar un número o booleano que llegará más tarde.
  • forEach ignora las promesas devueltas por funciones de devolución de llamada asíncronas.
  • Usa for...of para trabajo secuencial o map con Promise.all para trabajo independiente.
  • Una función de devolución de llamada anidada necesita su propia palabra clave async.
  • Mantén el trabajo que deliberadamente no esperas asociado a un manejador de rechazo.
5

Maneja errores en código asíncrono

Una operación fallida forma parte del flujo de control del programa. El código debe decidir si se recupera, reintenta, comunica el fallo o lo pasa a quien llama para que pueda decidir. Sigue los fallos a través de cadenas de promesas y llamadas esperadas, y después usa código de limpieza que se ejecute en cualquiera de los dos casos. Por último, coloca el manejo de errores en el límite de una acción para que el usuario reciba un mensaje útil y tú conserves la causa técnica.

5.1 Maneja rechazos en cadenas de promesas

Un rechazo de promesa sigue la cadena conectada hasta que un manejador de rechazo lo gestiona. Entender qué devuelve ese manejador te indica si la cadena se ha recuperado o sigue fallando.

Sigue un rechazo que pasa por manejadores de éxito
Promise.resolve(8)
  .then(value => {
    throw new Error("Count rejected");
  })
  .then(value => console.log("Unused: " + value))
  .catch(err => {
    console.log(err.message);
    return 2;
  })
  .then(value => console.log("Using " + value));

El lanzamiento rechaza la nueva promesa devuelta por el primer then. El siguiente manejador de cumplimiento se omite, por lo que nunca se registra «Unused». El catch registra «Count rejected» y devuelve 2. Ese retorno cumple el siguiente eslabón, por lo que el último manejador registra «Using 2».

Una promesa rechazada explícitamente y un error lanzado dentro de un manejador entran en la misma ruta de rechazo. Por eso un solo catch final puede manejar fallos de varios pasos conectados anteriores.

Elige entre recuperación o propagación

Un manejador de rechazo que devuelve un valor se recupera con ese valor. No devolver nada también recupera, pero con undefined. Por tanto, registrar un fallo no es lo mismo que mantener la cadena rechazada:

Promise.reject(new Error("Label missing"))
  .catch(err => console.log(err.message))
  .then(value => console.log(value));

El manejador final recibe undefined. Si el código posterior necesita una etiqueta real, esa recuperación silenciosa probablemente sea incorrecta. Devuelve una alternativa de respaldo utilizable o vuelve a lanzar el error.

function reportAndRethrow(err) {
  console.error(err);
  throw err;
}

Usar esta función en catch registra el error y deja la siguiente promesa rechazada con el mismo motivo. Quien llama más adelante en la cadena puede decidir qué hacer.

Coloca el manejador después del trabajo que protege

Un catch maneja los fallos anteriores a él. Si se recupera, los pasos posteriores se ejecutan normalmente y sus nuevos fallos necesitan un manejador posterior. Un manejador situado en medio no puede capturar errores que aún no han ocurrido.

También hay una diferencia importante entre .then(success, failure) y .then(success).catch(failure). Los dos manejadores de un mismo then responden al resultado de la promesa entrante. Su manejador de fallo no captura un error lanzado por su propio manejador de éxito. Ese error pertenece a la nueva promesa que devuelve la llamada, por lo que se necesita un catch posterior.

Conserva los errores útiles

Rechaza con objetos Error para que los manejadores puedan leer err.message y err.name. Lanzar una cadena o rechazar con ella está permitido, pero el código que espera los campos de un Error no los recibirá. Conserva el error original al propagar un fallo, salvo que añadir contexto ayude a quien llama a comprender qué operación falló.

Puntos clave

  • Los rechazos omiten los manejadores de cumplimiento hasta llegar a un manejador de rechazo.
  • Devolver desde catch recupera la cadena; no devolver nada la recupera con undefined.
  • Vuelve a lanzar el error para que el fallo siga propagándose.
  • Un catch protege los pasos conectados anteriores a él.
  • Un manejador de rechazo de then no puede capturar un error lanzado por el manejador de éxito del mismo then.

5.2 Usa try, catch y finally con await

try, catch y finally permiten a una función asíncrona manejar fallos en el punto donde los espera. Usa esta estructura para mantener juntos el manejo de resultados, la recuperación y la limpieza.

Un error transfiere el control

Un objeto Error describe un fallo. new Error("Label missing") tiene el mensaje «Label missing» y el nombre «Error»; las herramientas de depuración también pueden mostrar su pila. Los fallos integrados tienen tipos como TypeError.

throw detiene la ejecución normal en ese punto y transfiere el control al catch aplicable más cercano. El código posterior al manejo aún puede ejecutarse cuando la función captura su propio error. Lanza objetos Error para que los manejadores posteriores reciban campos útiles.

Espera dentro del bloque protegido
async function run() {
  console.log("Working");
  try {
    await Promise.reject(new Error("Device busy"));
    console.log("Complete");
  } catch (err) {
    console.log("Failed: " + err.message);
  } finally {
    console.log("Controls enabled");
  }
}
run().catch(console.error);

La promesa rechazada lanza un error en la línea de await. Se omite el resto de try. La salida es «Working», «Failed: Device busy» y luego «Controls enabled». Una operación exitosa llegaría a «Complete» y aun así ejecutaría la misma limpieza.

Usa finally para un indicador de carga, el estado de un botón u otro recurso que deba restablecerse después de cualquiera de los dos resultados. Se ejecuta incluso cuando try o catch tiene una sentencia return. Evita que la limpieza lance errores, salvo que se pretenda deliberadamente que ese nuevo fallo sustituya el resultado anterior. Un return en finally puede sobrescribir un return o un error lanzado anteriormente, así que evita usarlo para la limpieza habitual.

Aquí devolver una promesa es distinto de esperarla
async function readSetting() {
  try {
    return await Promise.reject(new Error("Missing setting"));
  } catch {
    return "light";
  }
}
readSetting().then(value => console.log(value));

Esto registra «light». La función espera dentro de try, por lo que el rechazo llega al catch local. Quitar await devuelve la promesa rechazada inmediatamente; la función adopta su rechazo, pero el catch local no maneja ese resultado posterior. Quien llama tendría que capturarlo.

Da a cada fallo un ámbito adecuado de captura

Un solo try puede cubrir un grupo de esperas cuando toda la acción deba detenerse si falla algún paso. Da a un paso opcional un ámbito más pequeño cuando pueda recuperarse de forma independiente. Por ejemplo, const settings = await loadSettings().catch(() => ({ theme: "light" })); usa un objeto predeterminado y continúa.

Una variable declarada dentro de try tiene ámbito de bloque. Si el código posterior al bloque la necesita, declara let result; antes de try o termina dentro de try el trabajo que depende del resultado. No muevas un valor fuera de su ámbito solo para mantener un registro final en una posición preferida.

Cuando la función actual no pueda recuperarse, permite que el rechazo llegue a quien llama. Capturarlo solo para ocultarlo impide que quien llama tome una decisión útil.

Puntos clave

  • Los rechazos esperados lanzan un error en la línea de await.
  • Catch puede recuperar, comunicar o volver a lanzar un error.
  • Finally se ejecuta después de cualquiera de los dos resultados, incluso con un return.
  • Usa return await dentro de try cuando el catch local deba manejar el rechazo de la operación devuelta.
  • Los resultados con ámbito de bloque permanecen dentro de su bloque try.

5.3 Mantén visibles los fallos y recupérate de forma controlada

Una acción necesita un ámbito de captura de errores: un lugar que reciba su fallo final y elija una respuesta. Sin ese ámbito, una promesa puede rechazarse sin que nadie maneje su resultado.

Maneja la ruta final

Un rechazo no manejado es un rechazo que sigue sin tener un manejador cuando el entorno anfitrión lo comprueba. Puede manejarse con catch, el argumento de rechazo de then o try/catch alrededor de await. Un navegador informa del problema y puede disparar unhandledrejection. Node.js se detiene de forma predeterminada ante rechazos no manejados según su política normal, por lo que dejar uno sin manejar puede terminar un programa.

Una acción como main() o un manejador de clic asíncrono debe manejar su promesa final. Las funciones de nivel inferior pueden transmitir los fallos hacia arriba, pero la ruta debe terminar en algún lugar que decida qué hacer. Un detector global de rechazos puede ayudar a registrar los fallos no detectados; no puede elegir la mejor recuperación para cada acción.

Un catch vacío silencia el fallo. Registra los detalles técnicos con console.error(err) y muestra un mensaje claro si la acción del usuario falló. Un mensaje como «No se pudo guardar la etiqueta. Vuelve a intentarlo dentro de un momento» indica al usuario qué puede hacer sin mostrar una traza de la pila.

Añade contexto sin perder la causa
async function prepareLabel() {
  try {
    return await Promise.reject(new Error("Device offline"));
  } catch (err) {
    throw new Error("Could not prepare label", { cause: err });
  }
}
prepareLabel().catch(err => {
  console.log(err.message);
  console.log(err.cause.message);
});

Esto registra «Could not prepare label» y «Device offline». El mensaje externo identifica la acción fallida. La propiedad cause conserva el error de nivel inferior para el diagnóstico. La opción de causa de Error forma parte de ECMAScript 2022.

Reintenta una operación nueva, con un límite
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
async function withRetry(task, attempts) {
  for (let attempt = 1; attempt <= attempts; attempt++) {
    try {
      return await task();
    } catch (err) {
      if (attempt === attempts) throw err;
      await wait(200);
    }
  }
}

Llama a esta función con una función que inicie un nuevo intento y un límite entero positivo. Si task falla dos veces y tiene éxito en su tercera llamada, un límite de cuatro se detiene en la tercera llamada y devuelve su valor. Si los cuatro intentos fallan, se vuelve a lanzar el cuarto error. El retraso solo ocurre entre intentos.

return await mantiene un rechazo dentro de este try, donde catch puede reintentarlo. Pasar una promesa ya rechazada en lugar de una función de tarea no reiniciaría el trabajo.

Reintenta cuando un fallo pueda ser temporal. Usa una alternativa de respaldo cuando la función pueda trabajar con datos predeterminados. Detén la acción e informa al usuario cuando no pueda completarse. Si una función de nivel inferior no puede tomar esa decisión, conserva el error y deja que quien llama decida.

Puntos clave

  • Toda acción asíncrona necesita un manejador para el fallo final.
  • Un catch vacío oculta información útil sobre el fallo.
  • La causa de un Error conserva el fallo original mientras añade contexto.
  • Reintenta llamando de nuevo a una tarea, con un límite finito de intentos.
  • Elige entre reintentar, usar una alternativa de respaldo, detenerte o propagar el error según el fallo.
6

Ejecuta tareas asíncronas en paralelo

Algunas operaciones necesitan resultados anteriores; otras pueden comenzar juntas. Identifica esa dependencia antes de elegir dónde poner await. Combinarás operaciones independientes, mantendrás los resultados en el orden correcto y elegirás si tu programa necesita todos los éxitos, todos los resultados, el primer establecimiento o el primer éxito. Cada elección cambia lo que significa un fallo y cuánto espera quien llama.

6.1 Inicia tareas juntas y espera con Promise.all

Las operaciones asíncronas independientes pueden esperar al mismo tiempo. La decisión de iniciarlas juntas depende de los datos que necesitan, no de añadir la palabra async.

Inicia operaciones independientes antes de esperarlas todas
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
function makePart(name, ms) {
  return wait(ms).then(() => name);
}
async function assemble() {
  const parts = await Promise.all([
    makePart("cover", 1000),
    makePart("pages", 2000),
    makePart("label", 1500)
  ]);
  console.log(parts.join(", "));
}
assemble().catch(console.error);

Las tres llamadas comienzan antes de que Promise.all espere. Unos 2000 ms después, el array contiene ["cover", "pages", "label"]. Esperar cada llamada antes de iniciar la siguiente tardaría unos 4500 ms. Los retrasos de los temporizadores son aproximados, por lo que los tiempos reales transcurridos pueden ser mayores.

Aquí en paralelo significa que las esperas se solapan. Los manejadores de promesas siguen ejecutándose uno a uno en el hilo principal. Llamar a tres funciones síncronas pesadas dentro del array ejecutaría esos cálculos uno tras otro; Promise.all no los trasladaría a hilos separados.

Los resultados conservan sus posiciones de entrada

La etiqueta termina antes que las páginas, pero sigue siendo la tercera en el array. Promise.all conserva el orden de entrada, no el orden de finalización. La desestructuración puede dar nombre a esas posiciones:

const [cover, pages] = await Promise.all([
  makePart("cover", 1000),
  makePart("pages", 2000)
]);

Usa ese código dentro de una función asíncrona. Para una colección, ids.map(id => loadItem(id)) inicia una operación que devuelve una promesa por cada identificador y proporciona a Promise.all el array de promesas. No esperes cada elemento dentro de un bucle si todos son independientes y deben solaparse.

Un fallo termina la espera combinada

Promise.all se rechaza en cuanto se rechaza una entrada. Si una parte falla después de unos 300 ms, un catch puede recibir ese motivo entonces; no necesita esperar a la parte exitosa más lenta.

Las demás operaciones siguen ejecutándose. Sus efectos secundarios aún pueden ocurrir, pero la promesa combinada no se cumplirá después con sus valores. Esta regla de rechazo inmediato ante el primer fallo es útil cuando el siguiente paso requiere todos los resultados.

Respeta las dependencias y asocia los manejadores sin demora

Si una operación necesita un usuario producido por otra, obtén primero el usuario. Las llamadas posteriores solo pueden combinarse si son independientes entre sí una vez que existe ese usuario.

Iniciar dos promesas y esperarlas por separado sí solapa sus esperas, pero la segunda puede rechazarse antes de que el código llegue a su espera. Si sigue sin tener un manejador cuando el entorno anfitrión lo comprueba, puede comunicarse un rechazo no manejado. Combinar las promesas sin demora con Promise.all asocia el manejo a ambas entradas y proporciona a quien llama una sola ruta de fallo.

Los valores normales están permitidos entre las entradas y conservan sus posiciones. Promise.all([]) ya está cumplida con un array vacío. Un manejador asociado a ella sigue ejecutándose de forma asíncrona.

Puntos clave

  • Inicia operaciones independientes juntas para solapar sus esperas.
  • Promise.all devuelve los resultados en el orden de entrada.
  • Un rechazo rechaza la promesa combinada sin cancelar las demás entradas.
  • Los pasos dependientes siguen necesitando sus resultados anteriores.
  • Combina las promesas iniciadas sin demora para mantener conectadas sus rutas de error.

6.2 Elige entre Promise.allSettled, Promise.race y Promise.any

El combinador de promesas adecuado depende de qué se considere un resultado útil. Decide si necesitas todos los éxitos, todos los resultados, el primer establecimiento o el primer éxito.

Conserva resultados parciales con allSettled

Promise.allSettled espera todas las entradas y devuelve registros de resultados en el orden de entrada. Un éxito se convierte en { status: "fulfilled", value }; un fallo se convierte en { status: "rejected", reason }. Una entrada rechazada no rechaza la promesa combinada.

Promise.allSettled([
  Promise.resolve("Map ready"),
  Promise.reject(new Error("Chart unavailable"))
]).then(results => {
  for (const result of results) {
    if (result.status === "fulfilled") console.log(result.value);
    else console.log(result.reason.message);
  }
});

Esto registra «Map ready» y «Chart unavailable». Es adecuado para paneles independientes donde un panel que funciona sigue siendo útil aunque otro falle. Lee value solo en un registro cumplido y reason en uno rechazado.

Deja que el primer establecimiento decida con race

Promise.race sigue la primera entrada que queda establecida, tanto si se cumple como si se rechaza. Un fallo después de 100 ms gana a un éxito después de 200 ms. Esto es útil para un tiempo límite:

function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
function timeout(ms) {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new Error("Time limit reached")), ms);
  });
}
Promise.race([
  wait(600).then(() => "Prepared"),
  timeout(250)
]).then(console.log).catch(err => console.log(err.message));

El tiempo límite se rechaza primero, por lo que la salida es «Time limit reached». La operación de 600 ms sigue ejecutándose. Una carrera establece la promesa combinada de quien llama; no cancela las operaciones que pierden.

Espera el primer éxito con any

Promise.any ignora las entradas fallidas mientras todavía sea posible un éxito. Si una operación falla después de 100 ms y otra se cumple después de 200 ms, da el valor de esta última. Un rechazo más rápido no gana.

Si todas las entradas se rechazan, any se rechaza con un error agregado (AggregateError). Su array errors conserva los motivos de rechazo en el orden de entrada. Esto permite a quien llama examinar por qué falló cada intento, incluso cuando los fallos llegaron en un orden diferente.

Compara las reglas de decisión
  • all: se necesita cada valor; se rechaza ante el primer rechazo.
  • allSettled: se necesita cada resultado; espera todos los registros.
  • race: decide el primer establecimiento, incluido un fallo.
  • any: decide el primer cumplimiento; solo se rechaza si todos se rechazan.

Sin entradas, all y allSettled se cumplen con []. any se rechaza con AggregateError porque no es posible ningún éxito. race permanece pendiente porque nada puede establecerla.

Los cuatro son compatibles con los navegadores actuales y Node.js. all y race entraron en ECMAScript en 2015, allSettled en 2020 y any en 2021.

Compara un par de resultados

Supón que ambas operaciones comienzan ahora: la primera entrada falla después de unos 100 ms, mientras que la segunda se cumple con «usable» después de unos 300 ms. Aplicar cada combinador a ese mismo par da resultados diferentes:

  • All se rechaza cerca de los 100 ms con el error de la primera entrada, porque todas las entradas deben tener éxito.
  • AllSettled se cumple cerca de los 300 ms con un registro rechazado seguido de uno cumplido, conservando las posiciones de entrada.
  • Race se rechaza cerca de los 100 ms porque ese es el primer establecimiento.
  • Any se cumple cerca de los 300 ms con «usable» porque el fallo anterior no puede proporcionar un éxito.

All y race se rechazan al mismo tiempo en este caso, pero usan reglas diferentes. Si la primera entrada rápida tuviera éxito y la segunda fallara más tarde, race conservaría su éxito temprano mientras que all se rechazaría por el fallo posterior. Elige según el resultado necesario, no según los tiempos de un ejemplo.

Puntos clave

  • allSettled informa de cada éxito y fallo en el orden de entrada.
  • race sigue el primer establecimiento, incluido un rechazo.
  • any acepta el primer éxito y solo se rechaza cuando todas las entradas se rechazan.
  • Un tiempo límite con race deja ejecutándose el trabajo que pierde.
  • Una entrada vacía en race permanece pendiente; una entrada vacía en any se rechaza.
  • Elige el combinador según el resultado que realmente necesita quien llama.

Ejemplos de JavaScript asíncrono

6 Lecciones Interactivas

1

Predice qué se ejecuta ahora y qué se ejecuta más tarde

14 pasos
Sigue un pequeño script a través de la pila y las colas y después usa el mismo modelo de tiempos para programar y cancelar temporizadores.
  1. 1
    IntroducciónDistingue el inicio de la finalización

    Empieza por separar las llamadas inmediatas de las funciones de devolución de llamada registradas. Un temporizador puede esperar sin ocupar el hilo principal, pero su función de devolución de llamada sigue necesitando ese hilo. Sigue lo que se ejecuta durante el script en la pila de llamadas antes de pasar al trabajo en cola, una tarea o una microtarea, porque su orden decide lo que ves primero.

  2. 2
    EjemploRegistra una función de devolución de llamada

    Lee este script antes de predecir su salida:

    console.log("request");
    setTimeout(() => console.log("ready"), 200);
    console.log("continue");
    

    El primer registro se ejecuta ahora. La llamada al temporizador registra una función y retorna, por lo que el script llega inmediatamente al registro final. El registro de la función de devolución de llamada pertenece a una tarea posterior; crear esa función no la ha llamado.

  3. 3
    Idea claveLa espera no ocupa el hilo

    La salida comienza con «request», «continue», seguido de «ready» después de unos 200 ms. El entorno anfitrión gestiona la espera, pero la función de devolución de llamada sigue necesitando el hilo principal. Un bucle largo puede retrasarla incluso después de que el temporizador esté listo.

  4. 4
    Desafío¿Puede interrumpir el temporizador?

    Un script registra un temporizador con retraso cero y luego realiza un cálculo síncrono largo. El temporizador queda listo antes de que termine el cálculo. ¿Puede ejecutarse su función de devolución de llamada durante ese cálculo?

    ---reveal---

    No. El cálculo mantiene ocupado el hilo principal hasta que termina el trabajo actual. Cuando el temporizador queda listo, su función de devolución de llamada entra en la cola para más tarde; eso no le da un hilo de ejecución separado ni permiso para interrumpir el cálculo.

  5. 5
    ExplicaciónDistingue dos tipos de trabajo posterior

    La función de devolución de llamada de un temporizador se ejecuta como una tarea. Un manejador de promesa se ejecuta como una microtarea. La pila de llamadas registra las llamadas que se están ejecutando.

    Después del trabajo actual, un punto de comprobación de microtareas vacía la cola de microtareas antes de que pueda comenzar otra tarea. Las microtareas añadidas durante el vaciado se incorporan a esa misma cola. Para seguir un script, mantén listas separadas para la salida inmediata, las microtareas y las tareas de temporizador.

  6. 6
    EjemploMarca las colas

    Ahora añade un manejador de promesa:

    console.log("request");
    setTimeout(() => console.log("timer"), 0);
    Promise.resolve().then(() => console.log("job"));
    console.log("continue");
    

    La salida inmediata es «request», «continue». El manejador de la promesa espera como microtarea; la función de devolución de llamada del temporizador es una tarea. Termina el script actual antes de ejecutar cualquiera de las funciones registradas.

  7. 7
    EjemploVacía la cola antes de la siguiente tarea

    Después de que termina el script, la microtarea registra «job». Solo cuando la cola de microtareas está vacía puede la tarea del temporizador listo registrar «timer».

    La salida completa es «request», «continue», «job», «timer». Una promesa cumplida no llama a su manejador de forma síncrona y un temporizador con retraso cero no se adelanta a las microtareas. El navegador puede renderizar en una oportunidad de renderizado después de este vaciado.

  8. 8
    DesafíoSitúa un trabajo recién añadido a la cola

    ¿Cuál es el orden de salida?

    Promise.resolve().then(() => console.log("a"))
      .then(() => console.log("c"));
    queueMicrotask(() => console.log("b"));
    console.log("now");
    

    ---reveal---

    «now», «a», «b», «c». El script coloca a antes de b en la cola. Solo cuando retorna el manejador de a queda listo el siguiente manejador de la cadena, por lo que c se coloca detrás del trabajo b que ya estaba en cola.

  9. 9
    ExplicaciónElige un temporizador y conserva su identificador

    Usa setTimeout para una llamada programada y setInterval para llamadas repetidas. Ambos devuelven un identificador que puedes guardar.

    const hint = setTimeout(() => console.log("Hint"), 800);
    clearTimeout(hint);
    

    Esto cancela la función de devolución de llamada antes de que se ejecute. Para un temporizador periódico, pasa su identificador a clearInterval cuando deba terminar la repetición. Pasa una función para programarla; llamarla en la lista de argumentos la ejecuta inmediatamente.

  10. 10
    ConsejoTen en cuenta las funciones que se ejecutan tarde

    Los retrasos se expresan en milisegundos y establecen una espera mínima. El código ocupado, las pestañas en segundo plano y los límites del navegador pueden demorar aún más las funciones de devolución de llamada. Programa un temporizador de ejecución única anidado después de la ejecución actual; espera también su trabajo asíncrono cuando las ejecuciones no deban solaparse.

  11. 11
    EjemploUsa el enlace de bucle adecuado

    Con let, cada iteración da a su función de devolución de llamada un índice separado:

    for (let n = 0; n < 3; n++) {
      setTimeout(() => console.log(n), 50);
    }
    

    Las funciones de devolución de llamada registran «0», «1», «2». Sustituir let por var hace que compartan una variable, cuyo valor es 3 después del bucle. Las tres registran entonces «3». La función de devolución de llamada lee el enlace cuando se ejecuta más tarde.

  12. 12
    DesafíoReinicia un temporizador de periodo de inactividad

    Un manejador usa un retraso de 300 ms para agrupar llamadas. Los eventos de entrada ocurren a los 0 ms, 80 ms y 170 ms, y no hay más eventos. ¿Aproximadamente cuándo se ejecuta su única acción?

    ---reveal---

    Aproximadamente 470 ms después del primer evento: 300 ms después del último evento, ocurrido a los 170 ms. Cada evento cancela el temporizador de ejecución única pendiente anterior y programa uno nuevo. Las acciones programadas anteriormente no se ejecutan también, y un hilo ocupado podría retrasar aún más la acción final.

  13. 13
    DesafíoRelaciona las reglas de tiempos

    Un temporizador está listo, pero cada microtarea sigue añadiendo otra microtarea a la cola. ¿Por qué no pueden avanzar ni la función de devolución de llamada del temporizador ni la siguiente actualización de pantalla?

    ---reveal---

    La cola de microtareas nunca termina de vaciarse, por lo que el bucle de eventos no puede pasar a la siguiente tarea ni a una oportunidad de renderizado. El retraso ya transcurrido del temporizador no altera ese orden. Programar microtareas interminables puede bloquear el avance igual que un bucle largo.

  14. 14
    RepasoUn seguimiento que puedes reutilizar

    Sigue primero el código inmediato, vacía después las microtareas y luego pasa a las tareas listas. Conserva los identificadores de temporizadores para cancelarlos. Los retrasos son aproximados; let conserva los valores de cada iteración, mientras que la agrupación de llamadas reinicia un retraso de periodo de inactividad.

2

Entrega un resultado mediante una función de devolución de llamada

9 pasos
Construye una API con una función de devolución de llamada y un solo resultado, añade una ruta de error y observa dónde corresponde el trabajo dependiente.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónDa a quien llama una siguiente acción
  2. 2
    EjemploProduce el valor diferido
  3. 3
    ConexiónLa API controla los tiempos
  4. 4
    Desafío¿De qué función se retorna?
  5. 5
    EjemploComunica uno de dos resultados
  6. 6
    ConsejoDetente antes de leer un resultado fallido
  7. 7
    DesafíoLocaliza el ámbito de captura de errores activo
  8. 8
    DesafíoConecta resultados y fallos entre pasos
  9. 9
    RepasoSigue cada resultado hasta quien lo consume
3

Construye y conecta resultados de promesas

12 pasos
Crea una promesa de pausa, asocia manejadores de resultados y repara una cadena dependiente rota.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónUn objeto para un resultado posterior
  2. 2
    EjemploConstruye una pausa reutilizable
  3. 3
    Idea claveDistingue el estado de la resolución
  4. 4
    DesafíoCaptura correctamente un fallo posterior
  5. 5
    ExplicaciónAsocia el manejador adecuado
  6. 6
    EjemploDos suscripciones conservan un valor
  7. 7
    DesafíoLee el resultado de una limpieza
  8. 8
    EjemploConstruye el primer eslabón
  9. 9
    EjemploHaz que espere el siguiente eslabón
  10. 10
    DesafíoRepara una operación desconectada
  11. 11
    DesafíoConecta la limpieza a una cadena dependiente
  12. 12
    RepasoComprueba la ruta devuelta
4

Reescribe una secuencia con async y await

12 pasos
Convierte una función de devolución de llamada en una operación que se pueda esperar y elige el bucle adecuado para varios elementos.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónConserva las promesas y cambia el orden de lectura
  2. 2
    EjemploUna llamada asíncrona comienza inmediatamente
  3. 3
    DesafíoLee el valor devuelto
  4. 4
    EjemploConecta una función de devolución de llamada a una promesa
  5. 5
    EjemploMantén los valores intermedios en el ámbito
  6. 6
    Desafío¿Adapta async por sí solo el resultado?
  7. 7
    Idea claveComprueba el valor, no su promesa
  8. 8
    EjemploforEach no espera las funciones de devolución de llamada
  9. 9
    ConsejoElige una estrategia para el bucle
  10. 10
    DesafíoCorrige la finalización prematura
  11. 11
    DesafíoRelaciona la adaptación y el bucle
  12. 12
    RepasoExamina ambos lados de una llamada asíncrona
5

Mantén conectados el fallo, la recuperación y la limpieza

13 pasos
Sigue una acción fallida a través de catch, añade una alternativa de respaldo local y construye una estrategia de reintentos limitada.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónDecide qué debe significar un fallo
  2. 2
    EjemploObserva cómo se recupera un catch
  3. 3
    Idea claveEl registro también determina un retorno
  4. 4
    DesafíoConserva el resultado fallido
  5. 5
    EjemploEspera donde catch pueda detectar el fallo
  6. 6
    ConsejoRetorna después del manejo local
  7. 7
    DesafíoElige la alternativa de respaldo local
  8. 8
    ConexiónTermina la ruta de error de la acción
  9. 9
    EjemploConserva el contexto y la causa original
  10. 10
    EjemploReintenta una nueva llamada, con un límite
  11. 11
    DesafíoDetente después del éxito
  12. 12
    DesafíoConecta la recuperación con la limpieza
  13. 13
    RepasoDa un destino a cada resultado
6

Elige qué debe entregar un grupo de tareas

11 pasos
Solapa esperas independientes y elige el combinador que corresponda al resultado necesario.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónEmpieza por la cuestión de las dependencias
  2. 2
    EjemploInicia ambas operaciones juntas
  3. 3
    Idea claveUn fallo no cancela el grupo
  4. 4
    DesafíoRespeta una dependencia y solapa el resto
  5. 5
    ExplicaciónConserva cada resultado cuando las partes sigan siendo útiles
  6. 6
    EjemploDistingue el primer establecimiento del primer éxito
  7. 7
    DesafíoElige para una página con resultados parciales
  8. 8
    ConsejoUn tiempo límite termina la espera, no la operación
  9. 9
    Desafío¿Qué puede establecer un grupo vacío?
  10. 10
    DesafíoRelaciona el momento de inicio y el manejo de fallos
  11. 11
    RepasoElige la estrategia de resultados

Ejercicios de JavaScript asíncrono

40 Preguntas de Práctica en 4 sets

Set 1: Sigue el código desde las llamadas hasta las funciones de devolución de llamada10 Preguntas de Práctica

Pregunta 1 de 10

Una página registra un temporizador y después ejecuta un cálculo síncrono largo. El temporizador queda listo durante el cálculo. ¿Cuándo puede ejecutarse su función de devolución de llamada?
  1. En cuanto transcurra el retraso solicitado al temporizador.
  2. Durante la siguiente iteración del bucle del cálculo.
  3. Después de que el cálculo libere el hilo principal.
  4. En un segundo hilo creado por la llamada al temporizador.
Mostrar respuestaOcultar respuesta
Respuesta: C
El cálculo mantiene ocupado el hilo principal hasta que termina, por lo que la función de devolución de llamada lista debe esperar. Que transcurra el retraso de un temporizador no puede interrumpir el JavaScript que se está ejecutando.

Pregunta 2 de 10

¿Cuál es el orden de salida?

console.log("open");
setTimeout(() => console.log("tick"), 0);
Promise.resolve().then(() => console.log("job"));
console.log("close");
  1. "open", "close", "job", "tick"
  2. "open", "tick", "job", "close"
  3. "open", "job", "close", "tick"
  4. "open", "close", "tick", "job"
Mostrar respuestaOcultar respuesta
Respuesta: A
El script actual registra «open» y «close» antes de que se ejecute cualquier función de devolución de llamada registrada. Su manejador de promesa es una microtarea, por lo que registra «job» antes de que la tarea del temporizador registre «tick». El retraso cero del temporizador permite que se ejecute más tarde; no lo coloca por delante del vaciado de microtareas.

Pregunta 3 de 10

Se ha programado un temporizador como const reminder = setTimeout(showHint, 900);. ¿Qué sentencia lo cancela antes de que se ejecute?
  1. clearTimeout(showHint);
  2. setTimeout(reminder, 0);
  3. clearInterval(showHint);
  4. clearTimeout(reminder);
Mostrar respuestaOcultar respuesta
Respuesta: D
ClearTimeout identifica el temporizador mediante el identificador devuelto, reminder. Pasar showHint proporciona la función, que no identifica este temporizador programado.

Pregunta 4 de 10

¿Qué registra este bucle después de que se ejecuten sus funciones de devolución de llamada?

for (var index = 2; index < 5; index++) {
  setTimeout(() => console.log(index), 0);
}
  1. 2, 3, 4
  2. 5, 5, 5
  3. 2, 2, 2
  4. 3, 4, 5
Mostrar respuestaOcultar respuesta
Respuesta: B
Todas las funciones de devolución de llamada capturan el mismo enlace var. Cuando se ejecutan, el bucle se ha detenido con index igual a 5, por lo que los tres registros leen 5. La secuencia 2, 3, 4 se produciría con un enlace let separado por iteración, no con esta variable compartida.

Pregunta 5 de 10

¿Cuántas llamadas a la función de devolución de llamada han terminado cuando este código llega al registro?

let calls = 0;
[3, 6, 9].map(n => {
  calls++;
  return n + 1;
});
console.log(calls);
  1. Han terminado tres llamadas.
  2. No ha terminado ninguna llamada.
  3. Solo ha terminado una llamada.
  4. Han terminado dos llamadas.
Mostrar respuestaOcultar respuesta
Respuesta: A
Map invoca las tres funciones de devolución de llamada antes de retornar, por lo que calls es 3 cuando se registra. Pasar una función de devolución de llamada no aplaza su ejecución cuando su API la invoca de forma síncrona.

Pregunta 6 de 10

¿Qué contiene result cuando se ejecuta el registro?

function make() {
  setTimeout(() => { return "badge"; }, 0);
}
const result = make();
console.log(result);
  1. La cadena "badge".
  2. Una promesa cumplida con "badge".
  3. undefined
  4. La función de devolución de llamada del temporizador.
Mostrar respuestaOcultar respuesta
Respuesta: C
Make no tiene una sentencia return y devuelve undefined. El return posterior solo sale de la función de devolución de llamada del temporizador; no puede proporcionar un valor a una llamada externa que ya ha terminado.

Pregunta 7 de 10

Una función de devolución de llamada con el error como primer argumento recibe un Error y ningún resultado si hay un fallo. ¿Qué impide que el código de éxito posterior a esta comprobación de error lea un item ausente?

if (err) {
  console.log(err.message);
  // Add one statement here.
}
console.log(item.name);
  1. err = null;
  2. return;
  3. console.log(item);
  4. callback = null;
Mostrar respuestaOcultar respuesta
Respuesta: B
Retornar permite salir de esta función de devolución de llamada después de comunicar el error, por lo que la línea item.name no se alcanza si hay un fallo. Asignar null a err solo cambia la variable local; no crea un resultado exitoso. La rama de error debe detener el procesamiento normal de éxito cuando no existe ningún elemento.

Pregunta 8 de 10

¿Cuál es el orden de salida?

Promise.resolve().then(() => {
  console.log("oak");
  queueMicrotask(() => console.log("elm"));
}).then(() => console.log("ash"));
queueMicrotask(() => console.log("fir"));
console.log("pine");
  1. "pine", "oak", "elm", "ash", "fir"
  2. "pine", "fir", "oak", "elm", "ash"
  3. "pine", "oak", "fir", "ash", "elm"
  4. "pine", "oak", "fir", "elm", "ash"
Mostrar respuestaOcultar respuesta
Respuesta: D
El script registra «pine» y coloca inicialmente oak antes que fir en la cola. El manejador de oak añade elm y después su retorno deja listo el manejador encadenado de ash, por lo que ambos se incorporan detrás de fir en ese orden. Ash no puede preceder a elm porque elm se añadió a la cola durante el manejador, antes de que se cumpliera la promesa devuelta.

Pregunta 9 de 10

Una operación con un solo resultado usa este cuerpo de temporizador. ¿Qué ocurre cuando id es 0 y qué cambio lo corrige?

if (id <= 0) callback(new Error("Invalid"));
callback(null, { id });
  1. Llama una vez; mueve la llamada de éxito antes de la comprobación.
  2. Llama dos veces; elimina el argumento de error de la primera llamada.
  3. Llama dos veces; retorna después de comunicar el error.
  4. Llama una vez; añade un return después de la llamada de éxito.
Mostrar respuestaOcultar respuesta
Respuesta: C
La condición de fallo ejecuta la primera llamada a la función de devolución de llamada, pero ninguna sentencia de control de flujo detiene la segunda llamada. Un return en la rama de fallo impide esa continuación y conserva el contrato de un solo resultado. Retornar después de la llamada de éxito es demasiado tarde porque ambas llamadas ya han ocurrido.

Pregunta 10 de 10

¿Cuál es el orden de salida?

console.log("outside");
const p = new Promise(resolve => {
  console.log("executor");
  resolve("value");
});
p.then(console.log);
console.log("registered");
  1. "outside", "executor", "registered", "value"
  2. "outside", "registered", "executor", "value"
  3. "outside", "executor", "value", "registered"
  4. "executor", "outside", "registered", "value"
Mostrar respuestaOcultar respuesta
Respuesta: A
El constructor Promise ejecuta su función ejecutora durante la construcción, entre los dos registros síncronos que la rodean. El manejador then se ejecuta más tarde como microtarea y registra «value» al final. Cumplir la promesa inmediatamente no hace que un manejador registrado con then se ejecute de forma síncrona.
Set 2: Pasa valores mediante promesas y await10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

¿Qué valor proporciona esta promesa a su manejador de cumplimiento?

const p = new Promise((resolve, reject) => {
  resolve(14);
  reject(new Error("Changed"));
  resolve(28);
});
  1. 28
  2. 14
  3. El Error con el mensaje "Changed".
  4. undefined
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

¿Qué se registra?

const p = Promise.resolve(7);
p.then(n => n + 4);
p.then(n => console.log(n));
  1. 11
  2. undefined
  3. Un objeto promesa.
  4. 7
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

¿Qué se registra?

Promise.resolve(6)
  .then(n => n - 2)
  .then(n => n * 5)
  .then(console.log);
  1. 20
  2. 30
  3. 4
  4. undefined
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Una función wait(ms) que devuelve una promesa se cumple después de unos ms milisegundos. ¿Qué llega a console.log y cuándo?

wait(120)
  .then(() => { wait(480).then(() => "ready"); })
  .then(console.log);
  1. "ready" después de unos 600 ms.
  2. undefined después de unos 600 ms.
  3. undefined después de unos 120 ms.
  4. "ready" después de unos 480 ms.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

¿Qué tipo de valor devuelve la llamada a esta función?

async function count() { return 9; }
  1. El número 9 directamente.
  2. Una promesa cumplida con undefined.
  3. Una función que devolverá 9.
  4. Una promesa cumplida con 9.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

¿Cuál es el orden de salida?

async function show() {
  console.log("inside");
  await 0;
  console.log("after await");
}
show();
console.log("caller");
  1. "caller", "inside", "after await"
  2. "inside", "caller", "after await"
  3. "inside", "after await", "caller"
  4. "caller", "after await", "inside"
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Una API basada en funciones de devolución de llamada llama a done(err, value) una vez. ¿Qué debe hacer su función envolvente basada en promesas cuando err es un Error?
  1. Llamar a resolve con err y detener esa función.
  2. Lanzar err después de que retorne la función diferida.
  3. Llamar a reject con err y detener esa función.
  4. Llamar a resolve con value y luego a reject con err.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

¿Qué se registra?

async function permitted() { return false; }
if (permitted()) console.log("enter");
else console.log("stop");
  1. "enter"
  2. "stop"
  3. false
  4. Nada hasta que la promesa quede establecida.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

record(id) devuelve una promesa y puede rechazarse. ¿Cómo debe esta función procesar los identificadores en orden, detenerse ante un fallo y permitir que quien llama capture ese fallo?

async function saveAll(ids) {
  ids.forEach(async id => { await record(id); });
}
  1. Esperar la llamada existente a forEach dentro de la función.
  2. Quitar await de record dentro de la función asíncrona existente.
  3. Usar map con funciones asíncronas, pero ignorar el array devuelto.
  4. Sustituir forEach por for...of y esperar record dentro del bucle.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

¿Qué valor registra el último manejador?

Promise.resolve(11)
  .finally(() => 40)
  .then(n => n + 2)
  .finally(() => Promise.resolve(90))
  .then(console.log);
  1. 42
  2. 13
  3. 90
  4. undefined
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 3: Maneja fallos y combina tareas independientes10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Un manejador catch devuelve "default" después de recibir un rechazo. ¿Qué recibe el siguiente manejador then?
  1. El motivo original del rechazo.
  2. Un valor indefinido pese al retorno.
  3. Una promesa rechazada que contiene "default".
  4. El valor de cumplimiento "default".
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

¿Qué se registra?

Promise.resolve("start")
  .then(() => { throw new Error("later"); },
        () => console.log("same call"))
  .catch(err => console.log(err.message));
  1. Solo "same call".
  2. Solo "later".
  3. "same call" y luego "later".
  4. "start" y luego "later".
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

¿Cuál es el orden de salida?

async function run() {
  try {
    await Promise.reject(new Error("busy"));
    console.log("saved");
  } catch (err) {
    console.log(err.message);
  } finally {
    console.log("reset");
  }
}
run();
  1. "saved", "reset"
  2. "reset", "busy"
  3. "busy", "reset"
  4. "busy", "saved", "reset"
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

¿Qué registra quien llama?

async function read() {
  try {
    return Promise.reject(new Error("missing"));
  } catch {
    return "local";
  }
}
read().then(console.log).catch(err => console.log(err.message));
  1. "missing"
  2. "local"
  3. undefined
  4. Nada porque la función permanece pendiente.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Una función envuelve un error como new Error("Report failed", { cause: original }). ¿Qué propiedad mantiene disponible el Error original?
  1. message
  2. name
  3. cause
  4. status
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Una función auxiliar de reintento espera una nueva tarea en try, retorna si tiene éxito y reintenta en catch hasta su límite de cuatro llamadas. La tarea se rechaza en las llamadas 1 y 2 y después se cumple con "ok" en la llamada 3. ¿Qué ocurre?
  1. Realiza cuatro llamadas y se cumple con "ok".
  2. Realiza una llamada y se rechaza con el primer error.
  3. Realiza tres llamadas y se rechaza con el segundo error.
  4. Realiza tres llamadas y se cumple con "ok".
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Dos funciones independientes basadas en temporizadores tardan unos 350 ms y 900 ms. Ambas se llaman dentro del array de entrada de await Promise.all(...) y ambas tienen éxito. ¿Cuánto tarda aproximadamente la espera combinada?
  1. Unos 900 ms.
  2. Unos 1250 ms.
  3. Unos 350 ms.
  4. Unos 550 ms.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Se inician juntas varias tareas: A se cumple después de 800 ms, B se rechaza después de 250 ms y C registra un efecto secundario y se cumple después de 600 ms. ¿Qué hace Promise.all([A, B, C])?
  1. Se rechaza cerca de los 800 ms; C aún puede registrar a los 600 ms.
  2. Se rechaza cerca de los 250 ms; C aún puede registrar a los 600 ms.
  3. Se rechaza cerca de los 250 ms; C se cancela antes de registrar.
  4. Se cumple cerca de los 800 ms con A y C, pero sin resultado de B.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Los paneles independientes deben mostrar cada uno su resultado o su propio error después de que terminen todos los intentos. ¿Qué combinador de promesas proporciona los registros necesarios?
  1. Promise.allSettled
  2. Promise.all
  3. Promise.race
  4. Promise.any
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Dos promesas comienzan juntas: una se rechaza después de unos 150 ms y otra se cumple con "backup" después de unos 400 ms. ¿En qué se diferencian race y any?
  1. Race se cumple cerca de 400 ms; any se rechaza cerca de 150 ms.
  2. Ambas se rechazan cerca de 150 ms con el primer fallo.
  3. Race se rechaza cerca de 150 ms; any se cumple cerca de 400 ms.
  4. Ambas se cumplen cerca de 400 ms con el valor de respaldo.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 4: Depura flujos asíncronos completos10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

¿Qué se ejecuta primero cuando se evalúa esta sentencia?

setTimeout(showMessage(), 500);
  1. La llamada a showMessage se ejecuta inmediatamente.
  2. El temporizador espera 500 ms antes de showMessage.
  3. El temporizador pone showMessage en cola como microtarea.
  4. Una promesa pendiente debe resolverse primero.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Una función envolvente asíncrona inicia una operación con una función de devolución de llamada que recibe el error como primer argumento, pero nunca devuelve una promesa conectada a ella. Su cuerpo llega al final inmediatamente. ¿Qué produce esperar la función envolvente?
  1. El valor exitoso que dará la función de devolución de llamada.
  2. El objeto Error que dará la función de devolución de llamada.
  3. Undefined antes de que esté listo el resultado de la función.
  4. Una espera que dura hasta que se llama a la función.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

inner sigue pendiente. ¿Qué ocurre aquí si inner se rechaza más tarde?

const outer = new Promise((resolve, reject) => {
  resolve(inner);
  reject(new Error("replacement"));
});
outer.catch(err => console.log(err.message));
  1. Outer se rechaza con "replacement" inmediatamente.
  2. Outer sigue el motivo de rechazo de inner.
  3. Outer se cumple con el objeto promesa inner.
  4. Outer permanece pendiente después del rechazo de inner.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

¿Cuál es el orden de salida?

async function step() {
  console.log("a");
  await null;
  console.log("b");
  queueMicrotask(() => console.log("c"));
}
setTimeout(() => console.log("d"), 0);
step();
Promise.resolve().then(() => console.log("e"));
console.log("f");
  1. "a", "f", "e", "b", "c", "d"
  2. "a", "f", "b", "c", "e", "d"
  3. "a", "b", "f", "e", "c", "d"
  4. "a", "f", "b", "e", "c", "d"
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

¿Qué registra el manejador final?

Promise.reject(new Error("first"))
  .catch(() => 4)
  .then(n => { throw new Error("second " + n); })
  .finally(() => 9)
  .catch(err => console.log(err.message));
  1. "first"
  2. "second 4"
  3. 9
  4. "second 9"
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Una llamada para obtener una cuenta tarda unos 200 ms. Después de que se cumple, dos llamadas independientes basadas en esa cuenta tardan unos 500 ms y 800 ms. Ambas necesitan la cuenta y se requieren todos los resultados. ¿Qué plan proporciona antes el resultado completo?
  1. Esperar la cuenta e iniciar ambas llamadas con Promise.all; unos 1000 ms.
  2. Iniciar las tres inmediatamente en Promise.all; unos 800 ms.
  3. Esperar la cuenta y luego cada llamada por turno; unos 1500 ms.
  4. Esperar la cuenta y luego usar race para las llamadas; unos 700 ms.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

¿Por qué esta función de reintento no reintenta una tarea que se rechaza de forma asíncrona?

async function retry(task) {
  for (let n = 0; n < 3; n++) {
    try { return task(); }
    catch (err) { if (n === 2) throw err; }
  }
}
  1. Captura el rechazo, pero sale porque el bucle comienza en cero.
  2. No puede reintentar porque task se pasa como función, no como promesa.
  3. Reintenta tres veces antes de que alguna promesa pueda establecerse.
  4. Devuelve la promesa antes de que su rechazo llegue al catch local.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

¿Cuál es el estado de Promise.race([]) cuando nunca se añadirán entradas?
  1. Se cumple con [].
  2. Se rechaza de inmediato.
  3. Permanece pendiente.
  4. Se resuelve en undefined.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Dos intentos comienzan juntos. La segunda entrada se rechaza primero; la primera se rechaza más tarde. ¿Qué proporciona finalmente Promise.any([first, second]) a catch?
  1. El Error de la segunda entrada en cuanto se rechaza.
  2. Un AggregateError con los motivos en orden: primero el de la primera y luego el de la segunda.
  3. Un AggregateError con los motivos en orden: primero el de la segunda y luego el de la primera.
  4. Solo el Error de la primera entrada después del rechazo posterior.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

¿Qué registra quien llama?

async function value() {
  try {
    return await Promise.reject(new Error("lost"));
  } catch {
    return "fallback";
  } finally {
    console.log("cleanup");
  }
}
value().then(console.log);
console.log("caller");
  1. "cleanup", "caller", "fallback"
  2. "caller", "fallback", "cleanup"
  3. "caller", "cleanup", "lost"
  4. "caller", "cleanup", "fallback"
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

¿Te atascas con una pregunta tuya? Ayuda con Tareas

Términos Clave

72 Términos Clave, cada uno con su definición

A

Adopción de promesas
La adopción de promesas hace que una promesa siga el valor de cumplimiento o el motivo de rechazo final de otra, en lugar de tratarla como un objeto normal.
Devolver una promesa desde then hace que el siguiente manejador espere y reciba su valor, sin una promesa anidada adicional.
Agrupación de llamadas tras un periodo de inactividad
La agrupación de llamadas tras un periodo de inactividad reinicia un temporizador con cada evento para que una acción espere un periodo sin actividad.
Escribir varios caracteres rápidamente sustituye cada vez el temporizador pendiente y deja una sola actualización después de que se deja de escribir.
Alternativa de respaldo
Una alternativa de respaldo es un valor o comportamiento alternativo usado después de un fallo cuando la acción mayor aún puede realizar trabajo útil sin su resultado original.
Una configuración opcional de tema ausente recurre a un tema claro mientras el resto de la página sigue cargándose.
await de nivel superior
El await de nivel superior es await usado directamente en el cuerpo de un módulo de JavaScript, donde el lenguaje lo permite fuera de una función asíncrona.
Un módulo puede esperar directamente su inicialización, mientras que una función normal sigue necesitando su propia palabra clave async.

Á

Ámbito de captura de errores
Un ámbito de captura de errores es el trabajo conectado cuyos fallos pueden llegar a un manejador catch concreto, según su ubicación y las llamadas esperadas.
Un catch situado en medio maneja los pasos anteriores, pero no puede manejar un nuevo error lanzado por un manejador posterior.

B

Bloque finally
Un bloque finally se ejecuta cuando el control sale de su estructura try/catch y proporciona limpieza después de un éxito, un fallo o una sentencia return.
Un botón se habilita en finally para que tanto los intentos exitosos como los fallidos dejen la interfaz lista para otra acción.
Bloque try
Un bloque try protege sentencias cuyos errores lanzados pueden transferir el control a su catch asociado.
Esperar una operación de preparación dentro de try permite que la función circundante se recupere cuando esa operación se rechaza más tarde.
Bloqueo
El bloqueo ocupa un hilo con trabajo e impide que otro código programado se ejecute en él.
Un bucle que mantiene ocupado el hilo puede retrasar un temporizador con retraso cero porque su función de devolución de llamada sigue necesitando el hilo principal ocupado.
Bucle de eventos
El bucle de eventos coordina las tareas, los puntos de comprobación de microtareas y las oportunidades de renderizado para que el trabajo listo pueda ejecutarse de forma ordenada.
Seguir primero un script y después sus microtareas explica por qué el mensaje de la promesa de un script aparece antes que el de su temporizador.

C

Cadena de promesas
Una cadena de promesas conecta manejadores mediante promesas devueltas y pasa cada valor devuelto o resultado asíncrono adoptado al siguiente eslabón.
Un manejador devuelve una promesa de preparación para que el siguiente reciba el elemento terminado cuando finalice la preparación.
Cadena sin anidamiento
Una cadena sin anidamiento expresa los pasos dependientes de promesas como manejadores sucesivos en el nivel externo, manteniendo conectados sus tiempos y la propagación de errores.
Devolver cada nueva operación permite que un catch al final reciba los fallos de los pasos conectados anteriores.
Causa del error
Una causa del error es un fallo original conservado en la propiedad cause de un nuevo Error.
Un error externo que dice «Report failed» sigue conteniendo, en su causa, el error del dispositivo que impidió la preparación del informe.
Código síncrono
El código síncrono termina cada sentencia antes de pasar a la siguiente.
Un bucle largo retrasa el siguiente registro porque cada iteración se ejecuta antes de que el control pueda salir del bucle.
Concurrencia
La concurrencia permite que las operaciones independientes sigan en curso durante esperas solapadas, mientras sus manejadores de JavaScript siguen ejecutándose uno a uno.
Dos temporizadores comienzan juntos, por lo que quien llama espera aproximadamente lo que tarda el más lento en lugar de sumar ambos retrasos.
Continuación
Una continuación es el código de una función asíncrona que se reanuda después de un await.
Incluso esperar un número normal aplaza las líneas restantes en lugar de ejecutarlas antes de que continúe quien llama.
Conversión a promesas
La conversión a promesas adapta una API basada en funciones de devolución de llamada a una API que devuelve promesas, conectando el éxito y el fallo de la función con el establecimiento de la promesa.
Una función envolvente de una API de devolución de llamada con etiqueta resuelve con la etiqueta o rechaza con el error para que quien llama desde código asíncrono pueda esperar la operación terminada.
Cumplida
Cumplida es el estado establecido de una promesa que contiene un valor de éxito.
Una comprobación exitosa de cantidad se cumple con la cantidad restante y su manejador de éxito recibe ese número.

E

Ejecución hasta completarse
La ejecución hasta completarse significa que, una vez iniciada una tarea o microtarea, las funciones de devolución de llamada puestas en cola de forma asíncrona no pueden interrumpir el código que se está ejecutando.
Que un temporizador quede listo a mitad de un bucle no inserta su función de devolución de llamada entre las sentencias del bucle.
Entorno anfitrión
El entorno anfitrión es el navegador o el entorno de ejecución de Node.js que proporciona recursos como los temporizadores y gestiona su programación.
El navegador controla el retraso de un temporizador y después deja disponible su función de devolución de llamada para que JavaScript la ejecute más tarde.
Error agregado
Un error agregado agrupa varios errores; Promise.any lo usa con un array errors en el orden de entrada cuando fallan todos los intentos.
Cuando dos intentos fallan en orden inverso, el array errors sigue enumerando sus motivos en el orden de entrada original.
Error silenciado
Un error silenciado es un fallo capturado que se oculta sin comunicarlo de forma útil ni propagarlo, lo que dificulta diagnosticar la acción fallida.
Un catch vacío en torno a una operación de guardado deja la página en silencio aunque el guardado no se haya completado con éxito.
Establecida
Una promesa establecida se ha cumplido o rechazado, por lo que su estado y resultado quedan fijados.
Volver a llamar a resolve después de que una promesa se haya cumplido no sustituye el valor que reciben sus manejadores.
Evento de error
Un evento de error comunica un fallo mediante un manejador de eventos, lo que permite a una API del navegador usar una ruta de fallo separada de su evento de éxito.
Una imagen tiene asignado su manejador de fallo antes de que su fuente comience a cargarse, junto con su manejador de éxito.

F

Función asíncrona
Una función asíncrona siempre devuelve una promesa para el resultado que devuelve o el error que lanza.
Llamar a una función asíncrona que devuelve un número da a quien llama una promesa, incluso sin un await en su cuerpo.
Función de devolución de llamada
Una función de devolución de llamada es una función que se pasa a otra para que esta la invoque.
Pasar un manejador con nombre proporciona una acción; añadir paréntesis lo llamaría al evaluar el argumento.
Función de devolución de llamada asíncrona
Una función de devolución de llamada asíncrona se invoca más tarde mediante programación, después de que la operación que la registró haya retornado a quien la llamó.
Una función de preparación de etiquetas retorna primero; su función de devolución de llamada del resultado recibe más tarde la etiqueta terminada.
Función de devolución de llamada con el error como primer argumento
Una función de devolución de llamada con el error como primer argumento recibe un argumento de error antes de su resultado: usa null para el éxito y un objeto Error para el fallo.
Quien llama comprueba el error antes de leer el elemento, para que una operación fallida no active el procesamiento de éxito.
Función de devolución de llamada de finally
Una función de devolución de llamada de finally se ejecuta después de cualquiera de los dos resultados de una promesa, no recibe argumentos y normalmente conserva el valor o motivo anterior.
La función de devolución de llamada oculta un indicador de carga después del éxito o del fallo; un error lanzado durante esa limpieza se convertiría en un nuevo fallo.
Función de devolución de llamada síncrona
Una función de devolución de llamada síncrona se invoca antes de que retorne la función que la recibe, por lo que su trabajo ocurre durante la llamada actual de esa función.
La función de devolución de llamada pasada a map duplica cada elemento antes de que se devuelva el array de resultados completo.
Función ejecutora
Una función ejecutora es la función que el constructor Promise llama inmediatamente con resolve y reject.
La función ejecutora registra un temporizador de inmediato; el manejador de éxito sigue ejecutándose más tarde mediante la cola de microtareas.
Función envolvente basada en promesas
Una función envolvente basada en promesas ofrece el resultado de otra API mediante una promesa devuelta, lo que permite a quienes llaman consumir el resultado con manejadores o await.
Envolver un temporizador crea una pausa reutilizable cuya finalización está conectada a la promesa devuelta a quien llama.

H

Hilo principal
El hilo principal ejecuta el JavaScript normal de la página y el trabajo relacionado con la pantalla.
Un cálculo largo en el hilo principal impide que un manejador de clic listo se ejecute hasta que termine el cálculo.

I

Identificador de temporizador
Un identificador de temporizador identifica un temporizador programado para que el programa pueda cancelarlo.
Guardar el identificador devuelto permite que el siguiente evento de entrada cancele la actualización de sugerencias pendiente anterior.
Inanición causada por microtareas
La inanición causada por microtareas retrasa las tareas y el renderizado porque la cola de microtareas nunca termina de vaciarse.
Una microtarea que siempre añade otra copia de sí misma a la cola puede impedir que un manejador de clic llegue a tener su turno.
Infierno de funciones de devolución de llamada
El infierno de funciones de devolución de llamada es código con un anidamiento profundo en el que cada paso dependiente añade otro nivel y, a menudo, otra rama de manejo de errores.
Leer un perfil, después proyectos y luego tareas crea varias funciones de devolución de llamada anidadas que son difíciles de examinar.

L

Lanzamiento de errores
Un lanzamiento de errores interrumpe la ejecución normal en ese punto y transfiere un error a un catch aplicable o lo propaga hacia fuera si no hay ninguno.
Una promesa rechazada que se espera se comporta como un lanzamiento de error en su línea de await y omite las sentencias restantes de try.

M

Manejador de cumplimiento
Un manejador de cumplimiento es una función registrada con then que recibe el valor de éxito de una promesa y determina el resultado de una nueva promesa.
Un manejador multiplica la cantidad recibida y devuelve la nueva cantidad para el siguiente eslabón de la cadena.
Manejador de rechazo
Un manejador de rechazo recibe el motivo de fallo de una promesa y puede recuperarse con un valor o conservar el fallo lanzando un error.
Un manejador catch proporciona una configuración predeterminada cuando una operación de configuración fallida no debe detener la acción mayor.
Método Promise.all
El método Promise.all combina las promesas de entrada en una sola promesa para todos los valores de éxito en el orden de entrada y se rechaza ante el primer rechazo de una entrada.
La primera entrada, más lenta, permanece en primer lugar en el array de resultados aunque la segunda termine antes.
Método Promise.allSettled
El método Promise.allSettled espera cada entrada y se cumple con registros en el orden de entrada que describen cada valor de cumplimiento o motivo de rechazo.
Una página conserva un panel exitoso y muestra un error para un panel fallido después de examinar ambos registros de resultados.
Método Promise.any
El método Promise.any se cumple con el primer valor de entrada exitoso y se rechaza con AggregateError solo cuando todas las entradas se han rechazado.
Un intento fallido temprano se ignora mientras otro intento aún tiene la posibilidad de proporcionar un resultado utilizable.
Método Promise.race
El método Promise.race sigue la primera entrada que queda establecida y toma su valor de éxito o su motivo de rechazo como resultado combinado.
Un tiempo límite que se rechaza gana frente a una operación exitosa más lenta, mientras que esta sigue ejecutándose.
Microtarea
Una microtarea es un trabajo en cola, como un manejador de promesa, que se ejecuta en un punto de comprobación de microtareas antes de que el bucle de eventos inicie otra tarea.
El manejador de una promesa cumplida se ejecuta después del script actual, pero antes de una tarea de temporizador con retraso cero que esté lista.

O

Objeto de error
Un objeto de error representa un fallo con un mensaje y un nombre, y suele incluir información de la pila útil para localizar el problema.
Leer el mensaje de un Error capturado creado con el mensaje «Missing label» da ese texto sin exigir que la interfaz de usuario muestre su pila.
Omisión de await
Una omisión de await deja una promesa en un lugar donde el código espera su valor final, lo que causa condiciones u operaciones numéricas incorrectas.
Multiplicar la promesa devuelta por una función de cantidad produce NaN en lugar de multiplicar su número futuro.
Omisión de return
Una omisión de return deja el resultado de un manejador como undefined y puede desconectar el trabajo que debía devolver.
Un manejador con llaves inicia una pausa, pero olvida devolverla, por lo que el siguiente manejador se ejecuta antes de que termine esa pausa.
Operación asíncrona
Una operación asíncrona comienza ahora y entrega su resultado más tarde, mientras otro código puede continuar.
Un temporizador se registra ahora y su función de devolución de llamada registra un mensaje después de que el entorno haya gestionado el retraso.
Operación dependiente
Una operación dependiente necesita el resultado de una operación anterior y solo debe comenzar cuando esa entrada necesaria esté disponible.
Una función de devolución de llamada recibe un usuario y después inicia la siguiente operación con ese usuario como argumento necesario.
Operaciones independientes
Las operaciones independientes pueden comenzar sin los resultados de las demás, lo que permite solapar sus tiempos de espera cuando el programa las inicia juntas.
La preparación de una cubierta y una etiqueta puede solaparse cuando ambas ya tienen todos los datos de entrada que necesitan.
Operador await
El operador await pausa su función asíncrona hasta que hay un resultado disponible y proporciona un valor de cumplimiento o lanza el motivo de rechazo en esa línea.
Esperar una pausa deja libre a quien llama para registrar un mensaje antes de que la función pausada reanude su siguiente sentencia.
Orden de entrada
El orden de entrada es la disposición de los valores o promesas proporcionados a un combinador.
Desestructurar el resultado de Promise.all da nombre a los valores según las posiciones del array original.

P

Pendiente
Pendiente es el estado de una promesa antes de su cumplimiento o rechazo.
Una pausa basada en un temporizador permanece pendiente mientras el entorno espera para invocar la función que la resuelve.
Pila de llamadas
La pila de llamadas registra las llamadas a funciones activas, con la llamada actual en la parte superior.
Si un manejador llama a una función auxiliar, esta se ejecuta en la parte superior de la pila hasta que retorna al manejador.
Procesamiento secuencial
El procesamiento secuencial inicia cada paso asíncrono después de que se complete el anterior, conservando las dependencias o un orden de operaciones requerido.
Un bucle for...of espera una operación de registro antes de iniciar la siguiente y registra la finalización después del último éxito.
Promesa
Una promesa es un objeto que representa un único resultado futuro y puede proporcionar un valor o un motivo de fallo a los manejadores conectados.
Una función devuelve una promesa inmediatamente y después un temporizador proporciona el valor que espera quien llamó.
Promesa ignorada devuelta por una función de devolución de llamada
Una promesa ignorada devuelta por una función de devolución de llamada es una promesa que la API que llama a esa función no usa para seguir la finalización o el fallo.
El método forEach ignora la promesa de una función de devolución de llamada asíncrona, por lo que la función que la contiene puede terminar antes que ese trabajo.
Propagación del rechazo
La propagación del rechazo transmite un fallo a través de eslabones de promesa conectados y omite los manejadores de cumplimiento hasta que un manejador de rechazo lo gestiona.
Un error lanzado en el segundo paso llega al catch final sin ejecutar el manejador de éxito del tercer paso.

R

Rechazada
Rechazada es el estado establecido de una promesa que contiene un motivo de fallo.
Una comprobación fallida de existencias se rechaza con un Error cuyo mensaje explica por qué la cantidad solicitada no está disponible.
Rechazo inmediato ante el primer fallo
El rechazo inmediato ante el primer fallo rechaza una espera combinada en cuanto falla una entrada necesaria, sin esperar a que terminen las entradas restantes.
Una preparación agrupada falla cuando una parte falla después de 200 ms, aunque otra parte sigue trabajando para completarse más tarde.
Rechazo no manejado
Un rechazo no manejado es un rechazo de promesa que sigue sin tener un manejador de rechazo cuando el entorno anfitrión realiza su comprobación de rechazos no manejados.
Una acción en segundo plano iniciada sin catch puede rechazarse después de que quien la llamó haya continuado, dejando su fallo sin manejar.
Recuperación
La recuperación convierte una ruta de promesa fallida en una exitosa al devolver un valor utilizable o una promesa cumplida desde un manejador de rechazo.
Devolver una etiqueta predeterminada en catch da al siguiente manejador de éxito un valor que puede usar para continuar.
Reintento
Un reintento inicia un nuevo intento después de un fallo, normalmente con un retraso y un límite finito de intentos cuando el fallo puede ser temporal.
Una tarea tiene éxito en su tercera llamada, por lo que una función auxiliar con cuatro intentos disponibles retorna sin realizar una cuarta llamada.
Relanzamiento de errores
El relanzamiento de errores consiste en volver a lanzar un error capturado para que un manejador posterior pueda recibirlo.
Un catch de nivel inferior registra un error y vuelve a lanzarlo, dejando que la acción externa se encargue del mensaje para el usuario.
Resuelta
Una promesa resuelta tiene su resultado decidido, posiblemente mediante la adopción de otra promesa que sigue pendiente y podría rechazarse más tarde.
Resolver una promesa envolvente con una promesa pendiente hace que la primera siga el éxito o fallo posterior de la segunda.
Resultado intermedio
Un resultado intermedio es un valor producido durante una secuencia y usado por un paso posterior.
Una función asíncrona almacena el usuario y después los pedidos, manteniendo ambos valores locales disponibles para el mensaje final.
Retorno anticipado
Un retorno anticipado sale de una función antes de que se ejecuten las sentencias restantes.
Retornar después de registrar un error de una función de devolución de llamada impide que la siguiente línea lea el nombre de un resultado ausente.

S

Suscripción a una promesa
Una suscripción a una promesa registra un manejador en una promesa concreta.
Dos manejadores de una promesa cumplida reciben cada uno su número original, aunque uno devuelva un número modificado.

T

Tarea
Una tarea es una unidad de trabajo programada, como un script, un manejador de eventos o una función de devolución de llamada de un temporizador.
El navegador termina el script actual antes de iniciar una función de devolución de llamada de un temporizador que ya está lista.
Temporizador de ejecución única
Un temporizador de ejecución única es una función de devolución de llamada programada una sola vez que puede ejecutarse después de un retraso, aunque la ejecución real también depende de la programación.
Una pista programada para los 600 ms puede aparecer más tarde si el hilo principal sigue realizando otro trabajo en ese momento.
Temporizador de ejecución única anidado
Un temporizador de ejecución única anidado programa otro temporizador de ejecución única desde una función de devolución de llamada de un temporizador, lo que permite que el siguiente retraso comience después del trabajo de la ejecución actual.
Una comprobación de progreso programa su siguiente temporizador solo después de que haya terminado su comprobación síncrona actual.
Temporizador periódico
Un temporizador periódico programa repetidamente una función de devolución de llamada hasta que se cancela.
Un contador cancela su temporizador periódico después de la quinta marca para que la página deje de producir mensajes de marca adicionales.

V

Valor verdadero en contexto booleano
Un valor verdadero en contexto booleano se trata como verdadero en una condición; una promesa lo es porque es un objeto, independientemente de su resultado final.
Una promesa que se cumple con false sigue entrando en una rama if cuando el código comprueba directamente el objeto promesa.

Sobre esta unidad

Por StudeliInformar de un error

Crea tus propios Materiales de Estudio

Convierte tus notas, diapositivas y documentos en Guías de Estudio, Preguntas de Práctica, Flashcards y 6 herramientas más.

Comenzar