Tratar con la pirámide de devolución de llamada node.js

9

Acabo de comenzar a usar el nodo, y una cosa que noté rápidamente es la rapidez con que las devoluciones de llamada pueden acumularse hasta un nivel tonto de sangría:

doStuff(arg1, arg2, function(err, result) {
    doMoreStuff(arg3, arg4, function(err, result) {
        doEvenMoreStuff(arg5, arg6, function(err, result) {
            omgHowDidIGetHere();
        });
    });
});

La guía de estilo oficial dice que poner cada devolución de llamada en una función separada, pero eso parece demasiado restrictivo en el uso de cierres, y hacer que un solo objeto declarado en el nivel superior esté disponible varias capas hacia abajo, ya que el objeto debe pasar por todos los devoluciones de llamada intermedias.

¿Está bien usar el alcance de la función para ayudar aquí? ¿Coloca todas las funciones de devolución de llamada que necesitan acceso a un objeto global-ish dentro de una función que declara ese objeto, por lo que se cierra?

function topLevelFunction(globalishObject, callback) {

    function doMoreStuffImpl(err, result) {
        doMoreStuff(arg5, arg6, function(err, result) {
            callback(null, globalishObject);
        });
    }

    doStuff(arg1, arg2, doMoreStuffImpl);
}

y así sucesivamente para varias capas más ...

¿O existen marcos, etc. para ayudar a reducir los niveles de sangría sin declarar una función con nombre para cada devolución de llamada? ¿Cómo manejas la pirámide de devolución de llamada?

thecoop
fuente
2
Tenga en cuenta un problema adicional con los cierres: en JS el cierre captura todo el contexto principal (en otros idiomas solo captura la variable utilizada o las que el usuario solicitó específicamente), lo que causa algunas pérdidas de memoria si la jerarquía de devolución de llamada es lo suficientemente profunda y si, por ejemplo, la devolución de llamada se retiene en alguna parte.
Eugene

Respuestas:

7

Las promesas proporcionan una separación clara de las preocupaciones entre el comportamiento asincrónico y la interfaz, de modo que las funciones asincrónicas se pueden invocar sin devoluciones de llamada, y la interacción de devolución de llamada se puede realizar en la interfaz genérica de promesa.

Hay múltiples implementaciones de "promesas":


Por ejemplo, puede reescribir estas devoluciones de llamada anidadas

http.get(url.parse("http://test.com/"), function(res) {
    console.log(res.statusCode);
    http.get(url.parse(res.headers["location"]), function(res) {
        console.log(res.statusCode);
    });
});

me gusta

httpGet(url.parse("http://test.com/")).then(function (res) {
    console.log(res.statusCode);
    return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
    console.log(res.statusCode);
});

En lugar de devolución de llamada en devolución de llamada, a(b(c()))se encadena el ".then" a().then(b()).then(c()).


Una introducción aquí: http://howtonode.org/promises

Fabien Sa
fuente
¿Le importaría explicar más sobre lo que hacen estos recursos y por qué los recomienda como respuesta a la pregunta que se hace? Las "respuestas de solo enlace" no son del todo bienvenidas en Stack Exchange
mosquito
1
OK lo siento. He agregado un ejemplo y más información.
Fabien Sa
3

Como alternativa a las promesas, debe echar un vistazo a la yieldpalabra clave en combinación con las funciones del generador que se presentarán en EcmaScript 6. Ambas están disponibles hoy en las compilaciones Node.js 0.11.x, pero requieren que especifique adicionalmente el --harmonyindicador cuando ejecute Node .js:

$ node --harmony app.js

El uso de estos constructos y una biblioteca como la de TJ Holowaychuk co le permiten escribir código asíncrono en un estilo que se ve como el código sincrónico, a pesar de que aún se ejecuta de forma asíncrona. Básicamente, estas cosas juntas implementan el soporte de rutina para Node.js.

Básicamente, lo que debe hacer es escribir una función generadora para el código que ejecuta cosas asincrónicas, llamar a las cosas asíncronas allí, pero prefijarlas con la yieldpalabra clave. Entonces, al final su código se ve así:

var run = function * () {
  var data = yield doSomethingAsync();
  console.log(data);
};

Para ejecutar esta función de generador, necesita una biblioteca como la mencionada anteriormente co. La llamada se ve así:

co(run);

O, para ponerlo en línea:

co(function * () {
  // ...
});

Tenga en cuenta que desde las funciones del generador puede llamar a otras funciones del generador, todo lo que necesita hacer es prefijarlas yieldnuevamente.

Para una introducción a este tema, busque en Google términos como yield generators es6 async nodejsy debería encontrar toneladas de información. Lleva un tiempo acostumbrarse, pero una vez que lo consigues, no querrás volver nunca más.

Tenga en cuenta que esto no solo le proporciona una sintaxis más agradable para llamar a las funciones, sino que también le permite utilizar la lógica de flujo de control habitual (sincrónica), como forbucles o try/ catch. No más perder el tiempo con muchas devoluciones de llamada y todas estas cosas.

Buena suerte y diviertete :-)!

Golo Roden
fuente
0

Ahora tiene el paquete asyncawait , con una sintaxis muy cercana a lo que debería ser el futuro soporte nativo de await& asyncin Node.

Básicamente, le permite escribir código asíncrono que parece síncrono , reduciendo drásticamente los niveles de sangría y LOC, con la compensación de una ligera pérdida de rendimiento (los números del propietario del paquete tienen una velocidad del 79% en comparación con las devoluciones de llamadas sin procesar), con suerte reducido cuando el soporte nativo estará disponible.

Sigue siendo una buena opción para salir del callback hell / pyramid of doom nightmare IMO, cuando el rendimiento no es la principal preocupación y el estilo de escritura sincronizada se adapta mejor a las necesidades de su proyecto.

Ejemplo básico del paquete doc:

var foo = async (function() {
    var resultA = await (firstAsyncCall());
    var resultB = await (secondAsyncCallUsing(resultA));
    var resultC = await (thirdAsyncCallUsing(resultB));
    return doSomethingWith(resultC);
});
Z escarchado
fuente