Resumen: ¿Existen algunos patrones de mejores prácticas bien establecidos que pueda seguir para mantener mi código legible a pesar de usar código asincrónico y devoluciones de llamada?
Estoy usando una biblioteca de JavaScript que hace muchas cosas de forma asíncrona y depende en gran medida de las devoluciones de llamada. Parece que escribir un método simple de "carga A, carga B, ..." se vuelve bastante complicado y difícil de seguir usando este patrón.
Permítanme dar un ejemplo (artificial). Digamos que quiero cargar un montón de imágenes (asincrónicamente) desde un servidor web remoto. En C # / async, escribiría algo como esto:
disableStartButton();
foreach (myData in myRepository) {
var result = await LoadImageAsync("http://my/server/GetImage?" + myData.Id);
if (result.Success) {
myData.Image = result.Data;
} else {
write("error loading Image " + myData.Id);
return;
}
}
write("success");
enableStartButton();
El diseño del código sigue el "flujo de eventos": primero, se deshabilita el botón de inicio, luego se cargan las imágenes ( await
asegura que la IU se mantenga receptiva) y luego se vuelve a habilitar el botón de inicio.
En JavaScript, usando devoluciones de llamada, se me ocurrió esto:
disableStartButton();
var count = myRepository.length;
function loadImage(i) {
if (i >= count) {
write("success");
enableStartButton();
return;
}
myData = myRepository[i];
LoadImageAsync("http://my/server/GetImage?" + myData.Id,
function(success, data) {
if (success) {
myData.Image = data;
} else {
write("error loading image " + myData.Id);
return;
}
loadImage(i+1);
}
);
}
loadImage(0);
Creo que los inconvenientes son obvios: tuve que volver a trabajar el bucle en una llamada recursiva, el código que se supone que debe ejecutarse al final está en algún lugar en el medio de la función, el código que comienza la descarga ( loadImage(0)
) está en la parte inferior, y generalmente es mucho más difícil de leer y seguir. Es feo y no me gusta.
Estoy seguro de que no soy el primero en encontrar este problema, así que mi pregunta es: ¿Existen algunos patrones de mejores prácticas bien establecidos que pueda seguir para mantener mi código legible a pesar de usar código asincrónico y devoluciones de llamada?
LoadImageAsync
de hecho es una llamadaExt.Ajax.request
de Sencha Touch.async.waterfall
es su respuesta.Respuestas:
Es muy poco probable que pueda lograr con js simple el mismo nivel de concisión y expresividad al trabajar con devoluciones de llamada que tiene C # 5. El compilador hace el trabajo al escribir toda esa plantilla para usted, y hasta que js runtimes lo haga, aún tendrá que pasar una devolución de llamada ocasional aquí y allá.
Sin embargo, es posible que no siempre desee reducir las devoluciones de llamada al nivel de simplicidad del código lineal: lanzar funciones no tiene que ser feo, hay un mundo entero trabajando con este tipo de código, y se mantienen cuerdas sin
async
yawait
.Por ejemplo, use funciones de orden superior (mi js puede estar un poco oxidado):
fuente
Me tomó un poco descifrar por qué lo haces de esta manera, pero creo que esto podría estar cerca de lo que quieres.
Primero apaga tantas solicitudes AJAX como puede hacer simultáneamente, y espera hasta que se completen todas antes de habilitar el botón Inicio. Esto será más rápido que una espera secuencial y, creo, es mucho más fácil de seguir.
EDITAR : tenga en cuenta que esto supone que tiene
.each()
disponible, y quemyRepository
es una matriz. Tenga cuidado con la iteración de bucle que usa aquí en lugar de eso, si no está disponible, esta aprovecha las propiedades de cierre para la devolución de llamada. Sin embargo, no estoy seguro de lo que tiene disponible, ya queLoadImageAsync
parece ser parte de una biblioteca especializada: no veo resultados en Google.fuente
.each()
disponible, y, ahora que lo mencionas, no es estrictamente necesario hacer la carga secuencialmente. Definitivamente voy a probar su solución. (Aunque aceptaré la respuesta de vski, ya que está más cerca de la pregunta original y más general).Descargo de responsabilidad: esta respuesta no responde específicamente a su problema, es una respuesta genérica a la pregunta: "¿Existen algunos patrones de mejores prácticas bien establecidos que pueda seguir para mantener mi código legible a pesar de usar código asincrónico y devoluciones de llamada?"
Por lo que sé, no hay un patrón "bien establecido" para manejar esto. Sin embargo, he visto dos tipos de métodos utilizados para evitar las pesadillas de devoluciones de llamada anidadas.
1 / Uso de funciones con nombre en lugar de devoluciones de llamadas anónimas
De esta manera, evita el anidamiento enviando la lógica de la función anónima en otra función.
2 / Uso de una biblioteca de gestión de flujo. Me gusta usar Step , pero es solo una cuestión de preferencia. Es el que usa LinkedIn, por cierto.
Uso una biblioteca de administración de flujo cuando uso muchas devoluciones de llamada anidadas, porque es mucho más legible cuando hay mucho código usándola.
fuente
En pocas palabras, JavaScript no tiene el azúcar sintáctico de
await
.Pero mover la parte "final" al final de la función es fácil; y con una función anónima que se ejecuta inmediatamente, podemos evitar declararle una referencia.
También puede pasar la parte "final" como una devolución de llamada exitosa a la función.
fuente