javascript - Cómo hacer que una función espere hasta que se haya llamado a una devolución de llamada utilizando node.js
multithreading callback (11)
Tengo una función simplificada que se parece a esto:
function(query) {
myApi.exec(''SomeCommand'', function(response) {
return response;
});
}
Básicamente, quiero que llame a myApi.exec
y devuelva la respuesta que se da en la devolución de llamada lambda. Sin embargo, el código anterior no funciona y simplemente vuelve inmediatamente.
Solo por un intento muy intrincado, probé lo siguiente que no funcionó, pero al menos tienes una 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, ¿cuál es una buena forma de ''node.js / event driven'' para hacer esto? Quiero que mi función espere hasta que se llame a la devolución de llamada, luego devolver el valor que se le pasó.
A mi me funcionó usar
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 asíncrona, mientras que el result[''key'']
no lo hizo.
Desde el nodo 4.8.0, puede utilizar la función de ES6 denominada generador. Puedes seguir este article para 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 debe estar bien como 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));
Eso anula el propósito de no bloquear la E / S: la estás bloqueando cuando no es necesario bloquear :)
Debe anidar sus devoluciones de llamada en lugar de obligar a node.js a esperar, o llamar a otra 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, piense que su arquitectura está mal.
La forma "de hacer un buen trabajo de node.js / event" es no esperar .
Al igual que casi todo lo demás cuando se trabaja con sistemas controlados por eventos como 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 se "devuelva" en el sentido normal, sino que envía la rutina que manejará 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 no lo uses así:
var returnValue = myFunction(query);
Pero así:
myFunction(query, function(returnValue) {
// use the return value here instead of like a regular (non-evented) return value
});
Si desea que las bibliotecas sofisticadas sean muy simples y fáciles, esperar a que se ejecuten las funciones de devolución de llamada en el nodo, antes de ejecutar algún 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();
}
Si no desea utilizar la devolución de llamada, puede usar 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 esto: https://github.com/kriskowal/q
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);
}
.....
});
};
Puedes hacer callbacks como este:
fetchPage(1, x = function(next, page) {
if (next) {
console.log("^^^ CALLBACK --> fetchPage: " + page);
fetchPage(page, x);
}
});
Una forma de lograr esto es envolver la llamada a la API en una promesa y luego usar await
para esperar el resultado.
// let''s say this is the API function with two callbacks,
// one for success and the other for error
function apiFunction(query, successCallback, errorCallback) {
if (query == "bad query") {
errorCallback("problem with the query");
}
successCallback("Your query was <" + query + ">");
}
// myFunction wraps the above API call into a Promise
// and handles the callbacks with resolve and reject
function apiFunctionWrapper(query) {
return new Promise((resolve, reject) => {
apiFunction(query,(successResponse) => {
resolve(successResponse);
}, (errorResponse) => {
reject(errorResponse)
});
});
}
// now you can use await to get the result from the wrapped api function
// and you can use standard try-catch to handle the errors
async function businessLogic() {
try {
const result = await apiFunctionWrapper("query all users");
console.log(result);
// the next line will fail
const result2 = await apiFunctionWrapper("bad query");
} catch(error) {
console.error("ERROR:" + error);
}
}
// call the main function
businessLogic();
Salida:
Your query was <query all users>
ERROR:problem with the query
compruebe esto: https://github.com/luciotato/waitfor-ES6
su código con wait.for: (requiere generadores, --harmony flag)
function* (query) {
var r = yield wait.for( myApi.exec, ''SomeCommand'');
return r;
}
Nota: Esta respuesta probablemente no debe usarse en el código de producción. Es un truco y deberías conocer las implicaciones.
Existe el módulo uvrun (actualizado para las versiones más recientes de Nodejs here ) donde puede ejecutar una sola ronda del bucle del evento principal libuv (que es el bucle principal de Nodejs).
Tu 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;
}
(También puede usar uvrun.runNoWait()
. Esto podría evitar algunos problemas con el bloqueo, pero requiere un 100% de CPU).
Tenga en cuenta que este tipo de enfoque invalida todo el propósito de Nodejs, es decir, tener todo asíncrono y no bloqueante. Además, podría aumentar mucho la profundidad de su pila de llamadas, por lo que podría terminar con desbordamientos de pila. Si ejecuta esa función recursivamente, definitivamente se encontrará con problemas.
Vea las otras respuestas sobre cómo rediseñar su código para hacerlo "bien".
Esta solución aquí es probablemente solo útil cuando haces pruebas y esp. Quiere tener código sincronizado y serial.
exports.dbtest = function (req, res) {
db.query(''SELECT * FROM users'', [], res, renderDbtest);
};
function renderDbtest(result, res){
res.render(''db'', { result: result });
}
Así es como lo hice, solo tienes que pasar "res" con él, para que puedas renderizar más tarde.