2011-02-15 17 views
186

tengo una función simplificada que tiene este aspecto:Cómo hacer una espera de función hasta que una devolución de llamada ha sido llamada usando Node.js

function(query) { 
    myApi.exec('SomeCommand', function(response) { 
    return response; 
    }); 
} 

Básicamente lo quiero llamar myApi.exec, y devolver la respuesta que es dado en la devolución de llamada lambda. Sin embargo, el código anterior no funciona y simplemente regresa inmediatamente.

Sólo por un intento muy hacker, probé el debajo del cual no funcionó, pero al menos usted consigue la idea de lo que estoy tratando de lograr:

function(query) { 
    var r; 
    myApi.exec('SomeCommand', function(response) { 
    r = response; 
    }); 
    while (!r) {} 
    return r; 
} 

Básicamente, lo que es un nodo buena' .js/¿Manera conducida por el evento de esto? Quiero que mi función espere hasta que se llame a la devolución de llamada, luego devuelva el valor que se le pasó.

+3

¿O lo estoy haciendo completamente de la manera incorrecta aquí, y debería llamar a otra devolución de llamada, en lugar de devolver una respuesta? – Chris

+0

Sí, exactamente. Una buena manera de resumir lo que estaba tratando de decir a continuación :) – Jakob

+0

[Este] (http://stackoverflow.com/a/437204/1804173) es, en mi opinión, la mejor explicación SO ** por qué ** el ciclo ocupado no funciona t trabajo. – bluenote10

Respuesta

226

La forma "buena node.js/event driven" de hacer esto es no espere.

Como casi todo lo demás cuando se trabaja con sistemas impulsados ​​por eventos como el nodo, su función debe aceptar un parámetro de devolución de llamada que se invocará cuando se complete el cálculo. La persona que llama no debe esperar a que el valor a ser "devueltos" en el sentido normal, sino más bien enviar la rutina que controlará el valor resultante:

function(query, callback) { 
    myApi.exec('SomeCommand', function(response) { 
    // other stuff here... 
    // bla bla.. 
    callback(response); // this will "return" your value to the original caller 
    }); 
} 

, así que ni utilizar de esta manera:

var returnValue = myFunction(query); 

Pero como esto:

myFunction(query, function(returnValue) { 
    // use the return value here instead of like a regular (non-evented) return value 
}); 
+3

Ok genial. ¿Qué ocurre si myApi.exec nunca llamó a la devolución de llamada? ¿Cómo lo haría para que se llame a la devolución de llamada después de decir 10 segundos con un valor de error que dice que ha cronometrado o algo así? – Chris

+1

Podría hacer algo como esto, por ejemplo: http://jsfiddle.net/LdaFw/ – Jakob

+5

O mejor aún (agregó un cheque para que la devolución de llamada no se pueda invocar dos veces): http://jsfiddle.net/LdaFw/1/ – Jakob

3

Esa derrota el propósito de no bloqueo IO - estás bloqueando cuando no es necesario bloquear :)

Usted debe anidar sus devoluciones de llamada En lugar de forzar Node.js que esperar o llamar a otro de devolución de llamada dentro de la devolución de llamada donde necesita el resultado de r.

Lo más probable es que, si necesita forzar el bloqueo, esté pensando en su arquitectura incorrecta.

+0

Tenía la sospecha de que tenía esto al revés. – Chris

+23

Lo más probable es que solo quiero escribir un script rápido para 'http.get()' alguna URL y 'console.log()' su contenido. ¿Por qué tengo que saltar hacia atrás para hacer eso en Node? –

+4

@DanDascalescu: ¿Y por qué tengo que declarar las firmas de tipo para hacerlo en idiomas estáticos? ¿Y por qué tengo que ponerlo en un método principal en lenguajes tipo C? ¿Y por qué tengo que compilarlo en un lenguaje compilado? Lo que estás cuestionando es una decisión de diseño fundamental en Node.js.Esa decisión tiene pros y contras. Si no te gusta, puedes usar otro idioma que se adapte mejor a tu estilo. Es por eso que tenemos más de uno. – Jakob

1

suponiendo que tiene una función:

var fetchPage(page, callback) { 
    .... 
    request(uri, function (error, response, body) { 
     .... 
     if (something_good) { 
      callback(true, page+1); 
     } else { 
      callback(false); 
     } 
     ..... 
    }); 


}; 

se puede hacer uso de las devoluciones de llamada de esta manera:

fetchPage(1, x = function(next, page) { 
if (next) { 
    console.log("^^^ CALLBACK --> fetchPage: " + page); 
    fetchPage(page, x); 
} 
}); 
18

cheque esto: https://github.com/luciotato/waitfor-ES6

el código con wait.for: (requiere generadores, bandera --harmony)

function* (query) { 
    var r = yield wait.for(myApi.exec, 'SomeCommand'); 
    return r; 
} 
+0

esta solución funcionó para mí –

5

Nota: Probablemente esta respuesta no deba usarse en el código de producción. Es un truco y debes saber sobre las implicaciones.

No es el módulo uvrun (actualizado si hay nuevas versiones nodejs here) donde se puede ejecutar una sola ronda de bucle de la libuv bucle de eventos principal (que es el nodejs bucle principal).

Su código se vería así:

function(query) { 
    var r; 
    myApi.exec('SomeCommand', function(response) { 
    r = response; 
    }); 
    var uvrun = require("uvrun"); 
    while (!r) 
    uvrun.runOnce(); 
    return r; 
} 

(podría usar alternativa uvrun.runNoWait() que podrían evitar algunos problemas con el bloqueo, sino que tiene 100% de la CPU..)

Tenga en cuenta que este enfoque tipo de invalida todo el propósito de Nodejs, es decir, tener todo asincrónico y no bloqueante. Además, puede aumentar mucho la profundidad de la pila de llamadas, por lo que puede terminar con desbordamientos de pila. Si ejecuta dicha función recursivamente, definitivamente encontrará problemas.

Vea las otras respuestas sobre cómo rediseñar su código para hacerlo "bien".

Esta solución aquí probablemente solo sea útil cuando realice pruebas y esp. quiero tener código sincronizado y serial.

1

A mi me funcionó a utilizar

JSON.parse(result)['key'] 

en el resultado que esperaba. Esto puede no ser "super general", pero al final "JSON.parse" logró la espera de la llamada asincrónica, mientras que result['key'] no lo hizo.

9

Si no desea utilizar la devolución de llamada, puede utilizar el módulo "Q".

Por ejemplo:

function getdb() { 
    var deferred = Q.defer(); 
    MongoClient.connect(databaseUrl, function(err, db) { 
     if (err) { 
      console.log("Problem connecting database"); 
      deferred.reject(new Error(err)); 
     } else { 
      var collection = db.collection("url"); 
      deferred.resolve(collection); 
     } 
    }); 
    return deferred.promise; 
} 


getdb().then(function(collection) { 
    // This function will be called afte getdb() will be executed. 

}).fail(function(err){ 
    // If Error accrued. 

}); 

Para obtener más información, consulte la siguiente: https://github.com/kriskowal/q

0
exports.dbtest = function (req, res) { 
    db.query('SELECT * FROM users', [], res, renderDbtest); 
}; 

function renderDbtest(result, res){ 
    res.render('db', { result: result }); 
} 

Aquí es cómo lo hice, sólo hay que pasar "res" con ella, por lo que puede hacer más tarde

5

Si quieres que sea muy simple y fácil, no hay bibliotecas extravagantes, para esperar a que se ejecuten las funciones de devolución de llamada en el nodo, antes de ejecutar otro código, es así:

//initialize a global var to control the callback state 
var callbackCount = 0; 
//call the function that has a callback 
someObj.executeCallback(function() { 
    callbackCount++; 
    runOtherCode(); 
}); 
someObj2.executeCallback(function() { 
    callbackCount++; 
    runOtherCode(); 
}); 

//call function that has to wait 
continueExec(); 

function continueExec() { 
    //here is the trick, wait until var callbackCount is set number of callback functions 
    if (callbackCount < 2) { 
     setTimeout(continueExec, 1000); 
     return; 
    } 
    //Finally, do what you need 
    doSomeThing(); 
} 
3

Desde el nodo 4.8.0 puede utilizar la función del generador llamado ES6. Puede seguir este article para obtener conceptos más profundos. Pero básicamente puedes usar generadores y promesas para hacer este trabajo. Estoy usando bluebird para promisificar y administrar el generador.

Su código debería estar bien como en el ejemplo a continuación.

const Promise = require('bluebird'); 

function* getResponse(query) { 
    const r = yield new Promise(resolve => myApi.exec('SomeCommand', resolve); 
    return r; 
} 

Promise.coroutine(getResponse)() 
    .then(response => console.log(response)); 
Cuestiones relacionadas