- Aprender
- Informática
- Programación
- Programación en JavaScript
- JavaScript asíncrono
JavaScript asíncrono
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
- 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íncronoExplica 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 eventosSigue 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 temporizadoresEjecuta 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.
- Distingue el código síncrono del asíncrono
- 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íncronasEscribe 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 excesivoUsa 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.
- Pasa funciones de devolución de llamada a operaciones asíncronas
- 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 promesaDescribe 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 finallyRegistra 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 secuenciaDevuelve 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.
- Explica los estados de las promesas y crea una promesa
- 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 awaitDeclara 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 awaitConvierte 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 awaitReconoce 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.
- Escribe funciones asíncronas y usa await
- 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 promesasSigue 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 awaitUsa 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 controladaEvita 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.
- Maneja rechazos en cadenas de promesas
- 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.allEjecuta 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.anyUsa 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.
- Inicia tareas juntas y espera con Promise.all
Guía de estudio de JavaScript asíncrono
1Explica 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");
- El script registra «begin».
- Registra una función de devolución de llamada de un temporizador; no la llama.
- Coloca el manejador de la promesa cumplida en la cola como microtarea.
- Registra «finish» y termina el trabajo actual.
- La microtarea registra «promise».
- 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.
2Escribe 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.
3Crea 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.
4Escribe 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.
5Maneja 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.
6Ejecuta 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
1Predice qué se ejecuta ahora y qué se ejecuta más tarde
14 pasos
- 1Introducció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.
- 2EjemploRegistra 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.
- 3Idea 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.
- 4Desafí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.
- 5Explicació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.
- 6EjemploMarca 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.
- 7EjemploVací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.
- 8Desafí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.
- 9ExplicaciónElige un temporizador y conserva su identificador
Usa
setTimeoutpara una llamada programada ysetIntervalpara 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
clearIntervalcuando deba terminar la repetición. Pasa una función para programarla; llamarla en la lista de argumentos la ejecuta inmediatamente. - 10ConsejoTen 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.
- 11EjemploUsa 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.
- 12Desafí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.
- 13Desafí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.
- 14RepasoUn 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.
2Entrega un resultado mediante una función de devolución de llamada
9 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónDa a quien llama una siguiente acción
- 2EjemploProduce el valor diferido
- 3ConexiónLa API controla los tiempos
- 4Desafío¿De qué función se retorna?
- 5EjemploComunica uno de dos resultados
- 6ConsejoDetente antes de leer un resultado fallido
- 7DesafíoLocaliza el ámbito de captura de errores activo
- 8DesafíoConecta resultados y fallos entre pasos
- 9RepasoSigue cada resultado hasta quien lo consume
3Construye y conecta resultados de promesas
12 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónUn objeto para un resultado posterior
- 2EjemploConstruye una pausa reutilizable
- 3Idea claveDistingue el estado de la resolución
- 4DesafíoCaptura correctamente un fallo posterior
- 5ExplicaciónAsocia el manejador adecuado
- 6EjemploDos suscripciones conservan un valor
- 7DesafíoLee el resultado de una limpieza
- 8EjemploConstruye el primer eslabón
- 9EjemploHaz que espere el siguiente eslabón
- 10DesafíoRepara una operación desconectada
- 11DesafíoConecta la limpieza a una cadena dependiente
- 12RepasoComprueba la ruta devuelta
4Reescribe una secuencia con async y await
12 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónConserva las promesas y cambia el orden de lectura
- 2EjemploUna llamada asíncrona comienza inmediatamente
- 3DesafíoLee el valor devuelto
- 4EjemploConecta una función de devolución de llamada a una promesa
- 5EjemploMantén los valores intermedios en el ámbito
- 6Desafío¿Adapta async por sí solo el resultado?
- 7Idea claveComprueba el valor, no su promesa
- 8EjemploforEach no espera las funciones de devolución de llamada
- 9ConsejoElige una estrategia para el bucle
- 10DesafíoCorrige la finalización prematura
- 11DesafíoRelaciona la adaptación y el bucle
- 12RepasoExamina ambos lados de una llamada asíncrona
5Mantén conectados el fallo, la recuperación y la limpieza
13 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónDecide qué debe significar un fallo
- 2EjemploObserva cómo se recupera un catch
- 3Idea claveEl registro también determina un retorno
- 4DesafíoConserva el resultado fallido
- 5EjemploEspera donde catch pueda detectar el fallo
- 6ConsejoRetorna después del manejo local
- 7DesafíoElige la alternativa de respaldo local
- 8ConexiónTermina la ruta de error de la acción
- 9EjemploConserva el contexto y la causa original
- 10EjemploReintenta una nueva llamada, con un límite
- 11DesafíoDetente después del éxito
- 12DesafíoConecta la recuperación con la limpieza
- 13RepasoDa un destino a cada resultado
6Elige qué debe entregar un grupo de tareas
11 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónEmpieza por la cuestión de las dependencias
- 2EjemploInicia ambas operaciones juntas
- 3Idea claveUn fallo no cancela el grupo
- 4DesafíoRespeta una dependencia y solapa el resto
- 5ExplicaciónConserva cada resultado cuando las partes sigan siendo útiles
- 6EjemploDistingue el primer establecimiento del primer éxito
- 7DesafíoElige para una página con resultados parciales
- 8ConsejoUn tiempo límite termina la espera, no la operación
- 9Desafío¿Qué puede establecer un grupo vacío?
- 10DesafíoRelaciona el momento de inicio y el manejo de fallos
- 11RepasoElige la estrategia de resultados
Ejercicios de JavaScript asíncrono
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
Mostrar respuestaOcultar respuesta
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");
Mostrar respuestaOcultar respuesta
Pregunta 3 de 10
const reminder = setTimeout(showHint, 900);. ¿Qué sentencia lo cancela antes de que se ejecute?Mostrar respuestaOcultar respuesta
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);
}
Mostrar respuestaOcultar respuesta
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);
Mostrar respuestaOcultar respuesta
Pregunta 6 de 10
¿Qué contiene result cuando se ejecuta el registro?
function make() {
setTimeout(() => { return "badge"; }, 0);
}
const result = make();
console.log(result);
Mostrar respuestaOcultar respuesta
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);
Mostrar respuestaOcultar respuesta
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");
Mostrar respuestaOcultar respuesta
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 });
Mostrar respuestaOcultar respuesta
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");
Mostrar respuestaOcultar respuesta
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);
});
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));
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);
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);
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; }
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");
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
done(err, value) una vez. ¿Qué debe hacer su función envolvente basada en promesas cuando err es un Error?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");
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); });
}
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);
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
"default" después de recibir un rechazo. ¿Qué recibe el siguiente manejador then?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));
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();
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));
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
new Error("Report failed", { cause: original }). ¿Qué propiedad mantiene disponible el Error original?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
"ok" en la llamada 3. ¿Qué ocurre?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
await Promise.all(...) y ambas tienen éxito. ¿Cuánto tarda aproximadamente la espera combinada?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Promise.all([A, B, C])?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
"backup" después de unos 400 ms. ¿En qué se diferencian race y any?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);
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
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));
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");
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));
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
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; }
}
}
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Promise.race([]) cuando nunca se añadirán entradas?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
Promise.any([first, second]) a catch?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");
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
¿Te atascas con una pregunta tuya? Ayuda con Tareas
Términos Clave
A
Adopción de promesas
Agrupación de llamadas tras un periodo de inactividad
Alternativa de respaldo
await de nivel superior
Á
Ámbito de captura de errores
B
Bloque finally
Bloque try
Bloqueo
Bucle de eventos
C
Cadena de promesas
Cadena sin anidamiento
Causa del error
Código síncrono
Concurrencia
Continuación
Conversión a promesas
Cumplida
E
Ejecución hasta completarse
Entorno anfitrión
Error agregado
Error silenciado
Establecida
Evento de error
F
Función asíncrona
Función de devolución de llamada
Función de devolución de llamada asíncrona
Función de devolución de llamada con el error como primer argumento
Función de devolución de llamada de finally
Función de devolución de llamada síncrona
map duplica cada elemento antes de que se devuelva el array de resultados completo.Función ejecutora
Función envolvente basada en promesas
H
Hilo principal
I
Identificador de temporizador
Inanición causada por microtareas
Infierno de funciones de devolución de llamada
L
Lanzamiento de errores
M
Manejador de cumplimiento
Manejador de rechazo
Método Promise.all
Método Promise.allSettled
Método Promise.any
Método Promise.race
Microtarea
O
Objeto de error
Omisión de await
Omisión de return
Operación asíncrona
Operación dependiente
Operaciones independientes
Operador await
Orden de entrada
P
Pendiente
Pila de llamadas
Procesamiento secuencial
Promesa
Promesa ignorada devuelta por una función de devolución de llamada
Propagación del rechazo
R
Rechazada
Rechazo inmediato ante el primer fallo
Rechazo no manejado
Recuperación
Reintento
Relanzamiento de errores
Resuelta
Resultado intermedio
Retorno anticipado
S
Suscripción a una promesa
T
Tarea
Temporizador de ejecución única
Temporizador de ejecución única anidado
Temporizador periódico
V
Valor verdadero en contexto booleano
Sobre esta unidad
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.