tablas - Patrón de diseño para gestionar múltiples operaciones de JavaScript asíncronas
practicas de javascript (3)
Aunque Promises nos brinda un código más limpio, podemos hacerlo mejor con Generators . Escribí una publicación sobre cómo usar generadores en Tame Async JavaScript con ES6 . El uso del patrón descrito facilitará el manejo de complejas interacciones asincrónicas y establecerá un modelo mental para las capacidades esperadas planificadas para ES7.
Estoy buscando un buen patrón de diseño para realizar un seguimiento de varias actividades de JavaScript asíncronas diferentes (carga de imágenes, múltiples llamadas AJAX, llamadas secuenciadas AJAX, etc.) que es mejor que muchas devoluciones de llamada personalizadas y variables de estado personalizadas. ¿Qué sugieres que use? ¿Hay algún tipo de sistema de cola con la capacidad de tener lógica más allá de la secuenciación?
Problema de ejemplo
Tengo una secuencia de inicio que implica una serie de procesos asíncronos (cargar imágenes, esperar temporizadores, hacer algunas llamadas ajax, hacer alguna inicialización). Algunos de los procesos de asincronización pueden iniciarse para ejecutarse al mismo tiempo (carga de imágenes, llamadas AJAX) y algunos tienen que ser secuenciados (ejecute la llamada AJAX n. ° 1, luego la llamada AJAX n. ° 2). En este momento, tengo todo funcionando con funciones de devolución de llamada y un montón de estado global que realiza un seguimiento de lo que se ha completado o no. Funciona, pero es bastante desordenado y he tenido algunos errores debido a la complicación de asegurarse de manejar todas las posibilidades de secuencia correcta y las condiciones de error.
Cuando tienes problemas, también es un gran problema depurar porque es como el principio de incertidumbre de Heisenberg. Tan pronto como establece un punto de interrupción en cualquier parte de la secuencia, todo cambia. Tienes que poner todo tipo de instrucciones de depuración en el código para tratar de discernir qué está sucediendo.
Aquí hay una descripción más específica:
Hay tres imágenes cargando. Tan pronto como se cargue una imagen específica, quiero mostrarla. Una vez que se ha mostrado durante un cierto período de tiempo, quiero mostrar la segunda imagen. El tercero entra en una cola para su posterior exhibición.
Hay tres llamadas AJAX que deben suceder en orden consecutivo (la salida de una se usa como parte de la entrada de la siguiente).
Cuando se realizan las llamadas AJAX, hay que procesar un montón de resultados de JS y luego se deben cargar dos imágenes más.
Cuando se cargan esas dos imágenes, hay algo más de pantalla que hacer.
Una vez hecho todo esto, examine cuánto tiempo se ha mostrado una de las imágenes y, si ha transcurrido el tiempo suficiente, visualice la siguiente. De lo contrario, espere un poco más antes de mostrar la siguiente imagen.
Cada paso tiene manejadores de errores y éxito. Algunos de los manejadores de errores activan un código alternativo que aún puede continuar exitosamente.
No espero que nadie siga el proceso exacto aquí, sino simplemente darles una idea del tipo de lógica entre los pasos.
Editar: Encontré AsyncQueue de YUI, que no es una solución completa para el tipo de problema que tengo, pero está en el mismo espacio. Parece ser más para secuenciar u ordenar un conjunto de operaciones asincrónicas, pero no veo cómo ayuda con el tipo de toma de decisiones que tengo.
Con las promesas ahora estándar en ES6 y muchas buenas bibliotecas que se extienden tanto para las nuevas características como para la compatibilidad con versiones anteriores, parece que las promesas son el camino a seguir aquí.
La solución comienza con tomar cada operación asíncrona y crear un contenedor que devuelve una promesa:
Para cargar una imagen:
function loadImage(url) {
return new Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() {
resolve(img);
};
img.onerror = img.onabort = function() {
reject(url);
};
img.src = url;
});
}
Para hacer una llamada Ajax (versión simplificada):
function ajaxGet(url) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest();
req.addEventListener("load", resolve);
req.addEventListener("error", reject);
req.addEventListener("abort", reject);
req.open("GET", url);
req.send();
});
}
Para esperar una cantidad de tiempo determinada antes de ejecutar una operación, puede incluso hacer una versión prometedora de setTimeout()
para que pueda encadenarse con otras operaciones prometedoras:
// delay, return a promise
// val is optional
function delay(t, val) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(val);
}, t);
});
}
Ahora, estos se pueden combinar para crear la lógica que la pregunta solicitó:
Hay tres imágenes cargando. Tan pronto como se cargue una imagen específica, quiero mostrarla. Una vez que se ha mostrado durante un cierto período de tiempo, quiero mostrar la segunda imagen. El tercero entra en una cola para su posterior exhibición.
// start all three images loading in parallel, get a promise for each one
var imagePromises = [url1, url2, url3].map(function(item) {
return loadImage(item);
});
// display the three images in sequence with a delay between them
imagePromises.reduce(function(p, item) {
return p.then(function() {
// when the next image is ready display it
return item.then(function(img) {
displayImage(img);
return delay(15 * 1000);
});
});
}, Promise.resolve());
Este uso de .reduce()
muestra un patrón de diseño clásico para secuenciar una serie de operaciones en una matriz utilizando promesas.
Hay tres llamadas AJAX que deben suceder en orden consecutivo (la salida de una se usa como parte de la entrada de la siguiente).
y
Cuando se realizan las llamadas AJAX, hay que procesar un montón de resultados de JS y luego se deben cargar dos imágenes más.
var p = ajaxGet(url1).then(function(results1) {
// do something with results1
return ajaxGet(url2);
}).then(function(results2) {
// do something with results2
return ajaxGet(url3);
}).then(function(results3) {
// process final results3 here
// now load two images and when they are loaded do some display stuff
return Promise.all(loadImage(imgx), loadImage(imgy)).then(function(imgs) {
doSomeDisplayStuff(imgs);
});
});
Eche un vistazo al concepto de Promises/A jQuery implementa esto con el objeto jQuery.Deferred
.
Aquí hay un buen article muestra cómo podría ser útil para su situación. Hace un tiempo hice una question similar.