javascript asynchronous functional-programming node.js

javascript - Cómo evitar la anidación larga de funciones asíncronas en Node.js



asynchronous functional-programming (23)

C # -like asyncawait es otra forma de hacer esto

https://github.com/yortus/asyncawait

async(function(){ var foo = await(bar()); var foo2 = await(bar2()); var foo3 = await(bar2()); }

Quiero hacer una página que muestre algunos datos de un DB, así que he creado algunas funciones que obtienen esa información de mi DB. Solo soy novato en Node.js, por lo que entiendo, si quiero usarlos todos en una sola página (respuesta HTTP), tendría que anidarlos todos:

http.createServer(function (req, res) { res.writeHead(200, {''Content-Type'': ''text/html''}); var html = "<h1>Demo page</h1>"; getSomeDate(client, function(someData) { html += "<p>"+ someData +"</p>"; getSomeOtherDate(client, function(someOtherData) { html += "<p>"+ someOtherData +"</p>"; getMoreData(client, function(moreData) { html += "<p>"+ moreData +"</p>"; res.write(html); res.end(); }); }); });

Si hay muchas funciones como esa, entonces la anidación se convierte en un problema .

Hay alguna manera de evitar esto? Supongo que tiene que ver con la forma en que combinas múltiples funciones asíncronas, lo que parece ser algo fundamental.


Después de que los demás respondieron, usted indicó que su problema eran las variables locales. Parece que una forma fácil de hacerlo es escribir una función externa para contener esas variables locales, luego usar un conjunto de funciones internas con nombre y acceder a ellas por su nombre. De esta forma, solo anidará dos veces, independientemente de cuántas funciones necesite encadenar.

Aquí está el intento de mi novato de usar el módulo mysql Node.js con anidación:

function with_connection(sql, bindings, cb) { pool.getConnection(function(err, conn) { if (err) { console.log("Error in with_connection (getConnection): " + JSON.stringify(err)); cb(true); return; } conn.query(sql, bindings, function(err, results) { if (err) { console.log("Error in with_connection (query): " + JSON.stringify(err)); cb(true); return; } console.log("with_connection results: " + JSON.stringify(results)); cb(false, results); }); }); }

Lo siguiente es una reescritura usando funciones internas nombradas. La función externa with_connection se puede usar como soporte para variables locales. (Aquí, tengo los parámetros sql , bindings , cb que actúan de manera similar, pero solo puedes definir algunas variables locales adicionales en with_connection ).

function with_connection(sql, bindings, cb) { function getConnectionCb(err, conn) { if (err) { console.log("Error in with_connection/getConnectionCb: " + JSON.stringify(err)); cb(true); return; } conn.query(sql, bindings, queryCb); } function queryCb(err, results) { if (err) { console.log("Error in with_connection/queryCb: " + JSON.stringify(err)); cb(true); return; } cb(false, results); } pool.getConnection(getConnectionCb); }

He estado pensando que tal vez sería posible crear un objeto con variables de instancia y usar estas variables de instancia como reemplazo de las variables locales. Pero ahora encuentro que el enfoque anterior que usa funciones anidadas y variables locales es más simple y más fácil de entender. Se necesita algo de tiempo para desaprender OO, parece :-)

Así que aquí está mi versión anterior con un objeto y variables de instancia.

function DbConnection(sql, bindings, cb) { this.sql = sql; this.bindings = bindings; this.cb = cb; } DbConnection.prototype.getConnection = function(err, conn) { var self = this; if (err) { console.log("Error in DbConnection.getConnection: " + JSON.stringify(err)); this.cb(true); return; } conn.query(this.sql, this.bindings, function(err, results) { self.query(err, results); }); } DbConnection.prototype.query = function(err, results) { var self = this; if (err) { console.log("Error in DbConnection.query: " + JSON.stringify(err)); self.cb(true); return; } console.log("DbConnection results: " + JSON.stringify(results)); self.cb(false, results); } function with_connection(sql, bindings, cb) { var dbc = new DbConnection(sql, bindings, cb); pool.getConnection(function (err, conn) { dbc.getConnection(err, conn); }); }

Resulta que bind se puede usar con alguna ventaja. Me permite deshacerme de las funciones anónimas algo feas que he creado y que no hicieron mucho, excepto para remitirme a una llamada a un método. No pude pasar el método directamente porque habría estado involucrado con el valor incorrecto de this . Pero con bind , puedo especificar el valor de this que quiero.

function DbConnection(sql, bindings, cb) { this.sql = sql; this.bindings = bindings; this.cb = cb; } DbConnection.prototype.getConnection = function(err, conn) { var f = this.query.bind(this); if (err) { console.log("Error in DbConnection.getConnection: " + JSON.stringify(err)); this.cb(true); return; } conn.query(this.sql, this.bindings, f); } DbConnection.prototype.query = function(err, results) { if (err) { console.log("Error in DbConnection.query: " + JSON.stringify(err)); this.cb(true); return; } console.log("DbConnection results: " + JSON.stringify(results)); this.cb(false, results); } // Get a connection from the pool, execute `sql` in it // with the given `bindings`. Invoke `cb(true)` on error, // invoke `cb(false, results)` on success. Here, // `results` is an array of results from the query. function with_connection(sql, bindings, cb) { var dbc = new DbConnection(sql, bindings, cb); var f = dbc.getConnection.bind(dbc); pool.getConnection(f); }

Por supuesto, nada de esto es correcto JS con codificación Node.js, solo pasé un par de horas en él. Pero tal vez con un poco de pulido, esta técnica puede ayudar?


El azúcar sintáctico más simple que he visto es la promesa de nodo.

npm install node-promise || git clone https://github.com/kriszyp/node-promise

Usando esto puedes encadenar métodos asíncronos como:

firstMethod().then(secondMethod).then(thirdMethod);

El valor de retorno de cada uno está disponible como argumento en el siguiente.


El infierno de devolución de llamada se puede evitar fácilmente en javascript puro con cierre. La siguiente solución supone que todas las devoluciones de llamada siguen la firma de la función (error, datos).

http.createServer(function (req, res) { var modeNext, onNext; // closure variable to keep track of next-callback-state modeNext = 0; // next-callback-handler onNext = function (error, data) { if (error) { modeNext = Infinity; } else { modeNext += 1; } switch (modeNext) { case 0: res.writeHead(200, {''Content-Type'': ''text/html''}); var html = "<h1>Demo page</h1>"; getSomeDate(client, onNext); break; // handle someData case 1: html += "<p>"+ data +"</p>"; getSomeOtherDate(client, onNext); break; // handle someOtherData case 2: html += "<p>"+ data +"</p>"; getMoreData(client, onNext); break; // handle moreData case 3: html += "<p>"+ data +"</p>"; res.write(html); res.end(); break; // general catch-all error-handler default: res.statusCode = 500; res.end(error.message + ''/n'' + error.stack); } }; onNext(); });


En su mayor parte, estaría de acuerdo con Daniel Vassallo. Si puede dividir una función complicada y profundamente anidada en funciones nombradas por separado, entonces esa suele ser una buena idea. Para los momentos en que tiene sentido hacerlo dentro de una función única, puede usar una de las muchas bibliotecas asíncronas de node.js disponibles. La gente ha ideado muchas formas diferentes de abordar esto, así que eche un vistazo a la página de módulos node.js y vea lo que piensa.

He escrito un módulo para esto yo mismo, llamado async.js . Usando esto, el ejemplo anterior podría actualizarse a:

http.createServer(function (req, res) { res.writeHead(200, {''Content-Type'': ''text/html''}); async.series({ someData: async.apply(getSomeDate, client), someOtherData: async.apply(getSomeOtherDate, client), moreData: async.apply(getMoreData, client) }, function (err, results) { var html = "<h1>Demo page</h1>"; html += "<p>" + results.someData + "</p>"; html += "<p>" + results.someOtherData + "</p>"; html += "<p>" + results.moreData + "</p>"; res.write(html); res.end(); }); });

Una cosa buena de este enfoque es que puede cambiar rápidamente su código para buscar los datos en paralelo cambiando la función ''serie'' a ''paralelo''. Además, async.js también funcionará dentro del navegador, por lo que puedes usar los mismos métodos que usarías en node.js si encontraras algún código asincrónico complicado.

Espero que sea útil!


Estoy enamorado de async.js desde que lo encontré. Tiene una función async.series que puede usar para evitar la anidación larga.

Documentación:-

series (tareas, [devolución de llamada])

Ejecute una serie de funciones en serie, cada una ejecutándose una vez que se haya completado la función anterior. [...]

Argumentos

tasks : una serie de funciones para ejecutar, a cada función se le pasa una devolución de llamada a la que debe llamar al finalizar. callback(err, [results]) - Una devolución de llamada opcional para ejecutar una vez que todas las funciones se hayan completado. Esta función obtiene una matriz de todos los argumentos pasados ​​a las devoluciones de llamada utilizadas en la matriz.

Así es como podemos aplicarlo a su código de ejemplo:

http.createServer(function (req, res) { res.writeHead(200, {''Content-Type'': ''text/html''}); var html = "<h1>Demo page</h1>"; async.series([ function (callback) { getSomeData(client, function (someData) { html += "<p>"+ someData +"</p>"; callback(); }); }, function (callback) { getSomeOtherData(client, function (someOtherData) { html += "<p>"+ someOtherData +"</p>"; callback(); }); }, funciton (callback) { getMoreData(client, function (moreData) { html += "<p>"+ moreData +"</p>"; callback(); }); } ], function () { res.write(html); res.end(); }); });


Kay, simplemente usa uno de estos módulos.

Esto cambiará esto:

dbGet(''userIdOf:bobvance'', function(userId) { dbSet(''user:'' + userId + '':email'', ''[email protected]'', function() { dbSet(''user:'' + userId + '':firstName'', ''Bob'', function() { dbSet(''user:'' + userId + '':lastName'', ''Vance'', function() { okWeAreDone(); }); }); }); });

Dentro de esto:

flow.exec( function() { dbGet(''userIdOf:bobvance'', this); },function(userId) { dbSet(''user:'' + userId + '':email'', ''[email protected]'', this.MULTI()); dbSet(''user:'' + userId + '':firstName'', ''Bob'', this.MULTI()); dbSet(''user:'' + userId + '':lastName'', ''Vance'', this.MULTI()); },function() { okWeAreDone() } );


Lo hago de una manera bastante primitiva pero efectiva. Por ejemplo, necesito obtener un modelo con sus padres e hijos y digamos que necesito hacer consultas separadas para ellos:

var getWithParents = function(id, next) { var getChildren = function(model, next) { /*... code ... */ return next.pop()(model, next); }, getParents = function(model, next) { /*... code ... */ return next.pop()(model, next); } getModel = function(id, next) { /*... code ... */ if (model) { // return next callbacl return next.pop()(model, next); } else { // return last callback return next.shift()(null, next); } } return getModel(id, [getParents, getChildren, next]); }


Lo que ha hecho allí es tomar un patrón de asincronización y aplicarlo a 3 funciones llamadas en secuencia, cada una de las cuales espera que la anterior se complete antes de comenzar, es decir, las ha hecho sincrónicas . El punto acerca de la programación asincrónica es que puede tener varias funciones funcionando todas a la vez y no tener que esperar a que se completen.

si getSomeDate () no proporciona nada para getSomeOtherDate (), que no proporciona nada para getMoreData (), entonces por qué no los llama asincrónicamente como js lo permite o si son interdependientes (y no asíncronos) escríbalos como un función única?

No necesita utilizar el anidamiento para controlar el flujo; por ejemplo, haga que cada función termine llamando a una función común que determina cuándo se completaron las 3 y luego envía la respuesta.


Lo que necesitas es un poco de azúcar sintáctico. Chek esto:

http.createServer(function (req, res) { res.writeHead(200, {''Content-Type'': ''text/html''}); var html = ["<h1>Demo page</h1>"]; var pushHTML = html.push.bind(html); Queue.push( getSomeData.partial(client, pushHTML) ); Queue.push( getSomeOtherData.partial(client, pushHTML) ); Queue.push( getMoreData.partial(client, pushHTML) ); Queue.push( function() { res.write(html.join('''')); res.end(); }); Queue.execute(); });

Bastante limpio , ¿no? Puede observar que html se convirtió en una matriz. Esto se debe en parte a que las cadenas son inmutables, por lo que es mejor que almacene su salida en una matriz, que descartar cadenas cada vez más grandes. La otra razón es debido a otra sintaxis agradable con bind .

Queue en el ejemplo es realmente solo un ejemplo y junto con partial se puede implementar de la siguiente manera

// Functional programming for the rescue Function.prototype.partial = function() { var fun = this, preArgs = Array.prototype.slice.call(arguments); return function() { fun.apply(null, preArgs.concat.apply(preArgs, arguments)); }; }; Queue = []; Queue.execute = function () { if (Queue.length) { Queue.shift()(Queue.execute); } };


Me gusta async.js mucho para este propósito.

El problema se resuelve con un comando en cascada:

cascada (tareas, [devolución de llamada])

Ejecuta una serie de funciones en serie, cada una pasando sus resultados a la siguiente en la matriz. Sin embargo, si alguna de las funciones pasa un error a la devolución de llamada, la siguiente función no se ejecuta y la devolución de llamada principal se invoca inmediatamente con el error.

Argumentos

tareas: una serie de funciones para ejecutar, a cada función se le pasa una devolución de llamada (err, resultado1, resultado2, ...) a la que debe llamar al finalizar. El primer argumento es un error (que puede ser nulo) y cualquier otro argumento se pasará como argumentos para la próxima tarea. devolución de llamada (err, [resultados]) - Una devolución de llamada opcional para ejecutar una vez que todas las funciones se hayan completado. Se pasarán los resultados de la devolución de llamada de la última tarea.

Ejemplo

async.waterfall([ function(callback){ callback(null, ''one'', ''two''); }, function(arg1, arg2, callback){ callback(null, ''three''); }, function(arg1, callback){ // arg1 now equals ''three'' callback(null, ''done''); } ], function (err, result) { // result now equals ''done'' });

En cuanto a las variables de respuesta, res, se compartirán dentro del mismo alcance que la función (req, res) {} que encerró toda la llamada async.waterfall.

No solo eso, async es muy limpio. Lo que quiero decir es que cambio muchos casos como este:

function(o,cb){ function2(o,function(err, resp){ cb(err,resp); }) }

Primero:

function(o,cb){ function2(o,cb); }

Luego a esto:

function2(o,cb);

Luego a esto:

async.waterfall([function2,function3,function4],optionalcb)

También permite llamar a muchas funciones prefabricadas preparadas para asincronizar desde util.js muy rápido. Simplemente encadena todo lo que quieras hacer, asegúrate de que o, cb se maneje de manera universal. Esto acelera todo el proceso de codificación.


Observación interesante Tenga en cuenta que en JavaScript normalmente puede reemplazar funciones de devolución de llamada en línea anónimas con variables de funciones nombradas.

El seguimiento:

http.createServer(function (req, res) { // inline callback function ... getSomeData(client, function (someData) { // another inline callback function ... getMoreData(client, function(moreData) { // one more inline callback function ... }); }); // etc ... });

Podría ser reescrito para que se vea así:

var moreDataParser = function (moreData) { // date parsing logic }; var someDataParser = function (someData) { // some data parsing logic getMoreData(client, moreDataParser); }; var createServerCallback = function (req, res) { // create server logic getSomeData(client, someDataParser); // etc ... }; http.createServer(createServerCallback);

Sin embargo, a menos que planee volver a usar la lógica de devolución de llamada en otros lugares, a menudo es mucho más fácil leer funciones anónimas en línea, como en su ejemplo. También le evitará tener que buscar un nombre para todas las devoluciones de llamada.

Además tenga en cuenta que como @pst menciona en un comentario a continuación, si está accediendo a variables de cierre dentro de las funciones internas, lo anterior no sería una traducción directa. En tales casos, usar funciones anónimas en línea es aún más preferible.


Para resolver este problema, escribí nodent ( https://npmjs.org/package/nodent ) que preprocesa invisiblemente su JS. Su código de ejemplo se convertiría (asíncrono, realmente - lea los documentos).

http.createServer(function (req, res) { res.writeHead(200, {''Content-Type'': ''text/html''}); var html = "<h1>Demo page</h1>"; someData <<= getSomeDate(client) ; html += "<p>"+ someData +"</p>"; someOtherData <<= getSomeOtherDate(client) ; html += "<p>"+ someOtherData +"</p>"; moreData <<= getMoreData(client) ; html += "<p>"+ moreData +"</p>"; res.write(html); res.end(); });

Claramente, hay muchas otras soluciones, pero el preprocesamiento tiene la ventaja de tener una sobrecarga de tiempo de ejecución pequeña o nula y, gracias al soporte del mapa de origen, también es fácil de depurar.


Puede usar este truco con una matriz en lugar de funciones anidadas o un módulo.

Mucho más fácil para los ojos.

var fs = require("fs"); var chain = [ function() { console.log("step1"); fs.stat("f1.js",chain.shift()); }, function(err, stats) { console.log("step2"); fs.stat("f2.js",chain.shift()); }, function(err, stats) { console.log("step3"); fs.stat("f2.js",chain.shift()); }, function(err, stats) { console.log("step4"); fs.stat("f2.js",chain.shift()); }, function(err, stats) { console.log("step5"); fs.stat("f2.js",chain.shift()); }, function(err, stats) { console.log("done"); }, ]; chain.shift()();

Puede ampliar el idioma para procesos paralelos o incluso cadenas paralelas de procesos:

var fs = require("fs"); var fork1 = 2, fork2 = 2, chain = [ function() { console.log("step1"); fs.stat("f1.js",chain.shift()); }, function(err, stats) { console.log("step2"); var next = chain.shift(); fs.stat("f2a.js",next); fs.stat("f2b.js",next); }, function(err, stats) { if ( --fork1 ) return; console.log("step3"); var next = chain.shift(); var chain1 = [ function() { console.log("step4aa"); fs.stat("f1.js",chain1.shift()); }, function(err, stats) { console.log("step4ab"); fs.stat("f1ab.js",next); }, ]; chain1.shift()(); var chain2 = [ function() { console.log("step4ba"); fs.stat("f1.js",chain2.shift()); }, function(err, stats) { console.log("step4bb"); fs.stat("f1ab.js",next); }, ]; chain2.shift()(); }, function(err, stats) { if ( --fork2 ) return; console.log("done"); }, ]; chain.shift()();


Recientemente, he creado una abstracción más simple llamada wait.for para llamar a las funciones asíncronas en el modo de sincronización (basado en Fibras). Está en una etapa inicial pero funciona. Es en:

https://github.com/luciotato/waitfor

Con wait.for , puede llamar a cualquier función noda estándar de nodejs, como si fuera una función de sincronización.

usando wait.for su código podría ser:

var http=require(''http''); var wait=require(''wait.for''); http.createServer(function(req, res) { wait.launchFiber(handleRequest,req, res); //run in a Fiber, keep node spinning }).listen(8080); //in a fiber function handleRequest(req, res) { res.writeHead(200, {''Content-Type'': ''text/html''}); var html = "<h1>Demo page</h1>"; var someData = wait.for(getSomeDate,client); html += "<p>"+ someData +"</p>"; var someOtherData = wait.for(getSomeOtherDate,client); html += "<p>"+ someOtherData +"</p>"; var moreData = wait.for(getMoreData,client); html += "<p>"+ moreData +"</p>"; res.write(html); res.end(); };

... o si quieres ser menos detallado (y también agregar error de captura)

//in a fiber function handleRequest(req, res) { try { res.writeHead(200, {''Content-Type'': ''text/html''}); res.write( "<h1>Demo page</h1>" + "<p>"+ wait.for(getSomeDate,client) +"</p>" + "<p>"+ wait.for(getSomeOtherDate,client) +"</p>" + "<p>"+ wait.for(getMoreData,client) +"</p>" ); res.end(); } catch(err) { res.end(''error ''+e.message); } };

En todos los casos, getSomeDate , getSomeOtherDate y getMoreData deben ser funciones asíncronas estándar con el último parámetro, una devolución de llamada a la función (err, data)

como en:

function getMoreData(client, callback){ db.execute(''select moredata from thedata where client_id=?'',[client.id], ,function(err,data){ if (err) callback(err); callback (null,data); }); }



Supongamos que puede hacer esto:

http.createServer(function (req, res) { res.writeHead(200, {''Content-Type'': ''text/html''}); var html = "<h1>Demo page</h1>"; chain([ function (next) { getSomeDate(client, next); }, function (next, someData) { html += "<p>"+ someData +"</p>"; getSomeOtherDate(client, next); }, function (next, someOtherData) { html += "<p>"+ someOtherData +"</p>"; getMoreData(client, next); }, function (next, moreData) { html += "<p>"+ moreData +"</p>"; res.write(html); res.end(); } ]); });

Solo necesita implementar chain () para que aplique parcialmente cada función al siguiente e invoque inmediatamente solo la primera función:

function chain(fs) { var f = function () {}; for (var i = fs.length - 1; i >= 0; i--) { f = fs[i].partial(f); } f(); }


Usando un wire tu código se vería así:

http.createServer(function (req, res) { res.writeHead(200, {''Content-Type'': ''text/html''}); var l = new Wire(); getSomeDate(client, l.branch(''someData'')); getSomeOtherDate(client, l.branch(''someOtherData'')); getMoreData(client, l.branch(''moreData'')); l.success(function(r) { res.write("<h1>Demo page</h1>"+ "<p>"+ r[''someData''] +"</p>"+ "<p>"+ r[''someOtherData''] +"</p>"+ "<p>"+ r[''moreData''] +"</p>"); res.end(); }); });


Use Fibras https://github.com/laverdet/node-fibers hace que el código asíncrono se vea como sincrónico (sin bloqueo)

Personalmente utilizo este pequeño envoltorio http://alexeypetrushin.github.com/synchronize Muestra de código de mi proyecto (cada método es realmente asincrónico, trabajando con el archivo asíncrono IO) Incluso me da miedo imaginar qué desastre sería con la devolución de llamada o async-control-flow helper libraries.

_update: (version, changesBasePath, changes, oldSite) -> @log ''updating...'' @_updateIndex version, changes @_updateFiles version, changesBasePath, changes @_updateFilesIndexes version, changes configChanged = @_updateConfig version, changes @_updateModules version, changes, oldSite, configChanged @_saveIndex version @log "updated to #{version} version"


Yo tuve el mismo problema. He visto las principales librerías para ejecutar funciones asíncronas de nodos, y presentan un encadenamiento tan natural (necesitas usar tres o más métodos confs, etc.) para construir tu código.

Pasé algunas semanas desarrollando una solución para ser simple y facilitar la lectura. Por favor, prueba con EnqJS . Todas las opiniones serán apreciadas.

En lugar de:

http.createServer(function (req, res) { res.writeHead(200, {''Content-Type'': ''text/html''}); var html = "<h1>Demo page</h1>"; getSomeDate(client, function(someData) { html += "<p>"+ someData +"</p>"; getSomeOtherDate(client, function(someOtherData) { html += "<p>"+ someOtherData +"</p>"; getMoreData(client, function(moreData) { html += "<p>"+ moreData +"</p>"; res.write(html); res.end(); }); }); });

con EnqJS:

http.createServer(function (req, res) { res.writeHead(200, {''Content-Type'': ''text/html''}); var html = "<h1>Demo page</h1>"; enq(function(){ var self=this; getSomeDate(client, function(someData){ html += "<p>"+ someData +"</p>"; self.return(); }) })(function(){ var self=this; getSomeOtherDate(client, function(someOtherData){ html += "<p>"+ someOtherData +"</p>"; self.return(); }) })(function(){ var self=this; getMoreData(client, function(moreData) { html += "<p>"+ moreData +"</p>"; self.return(); res.write(html); res.end(); }); }); });

Observe que el código parece ser más grande que antes. Pero no está anidado como antes. Para parecer más natural, las cadenas se llaman de inmediato:

enq(fn1)(fn2)(fn3)(fn4)(fn4)(...)

Y decir que volvió, dentro de la función que llamamos:

this.return(response)



para su conocimiento, considere Jazz.js https://github.com/Javanile/Jazz.js/wiki/Script-showcase

const jj = require(''jazz.js''); // ultra-compat stack jj.script([ a => ProcessTaskOneCallbackAtEnd(a), b => ProcessTaskTwoCallbackAtEnd(b), c => ProcessTaskThreeCallbackAtEnd(c), d => ProcessTaskFourCallbackAtEnd(d), e => ProcessTaskFiveCallbackAtEnd(e), ]);


Task.js te ofrece esto:

spawn(function*() { try { var [foo, bar] = yield join(read("foo.json"), read("bar.json")).timeout(1000); render(foo); render(bar); } catch (e) { console.log("read failed: " + e); } });

En lugar de esto:

var foo, bar; var tid = setTimeout(function() { failure(new Error("timed out")) }, 1000); var xhr1 = makeXHR("foo.json", function(txt) { foo = txt; success() }, function(err) { failure() }); var xhr2 = makeXHR("bar.json", function(txt) { bar = txt; success() }, function(e) { failure(e) }); function success() { if (typeof foo === "string" && typeof bar === "string") { cancelTimeout(tid); xhr1 = xhr2 = null; render(foo); render(bar); } } function failure(e) { xhr1 && xhr1.abort(); xhr1 = null; xhr2 && xhr2.abort(); xhr2 = null; console.log("read failed: " + e); }