Para solucionar este problema tenemos los callbacks, que nos ayudan a esperar, si nuestra información depende de ese resultado. Los callbacks por convención son una función que recibe un parámetro de error y uno de éxito.
Callbacks en NodeJS
Los callback son muy importantes en NodeJS ya que permiten pasar una función como argumento a otra función, gracias a esto podemos ejecutar una tarea después de que se complete otra que puede ser asíncrona o síncrona. Además los métodos de los diferentes módulos fueron construidos utilizando callback, lo que significa que trabajar con promesas u asycn/await puede llegar a ser un poco mas difícil.
promesas
o async/await
.
En este ejemplo podemos ver una función que bloquea el sistema hasta que se resuelve (es una mala practica):
function fetchData() {
// El sistema se bloquea hasta completar el for
for(let index = 0; index < 1e9; index++) {
// Blocking code
}
return 2;
}
function processData() {
var data = fetchData();
data += 1;
console.log(data)
return data;
}
processData()
// 3
En nodejs tratamos de crear código no bloqueante, es por eso que debemos buscar la mejor aproximación ya sea usar un event Emitter o otra estrategia (promesas, async/await).
En este ejemplo fetchData
no bloquea el sistema, sin embargo el código no va entregarnos resultado porque mientras se procesa la respuesta ya se esta ejecutando la suma.
function fetchData () {
// Se simula código no bloqueante
setTimeout(() => {
return 2;
}, 1000);
}
function processData () {
var data = fetchData();
data += 1;
console.log(data)
return data;
}
processData ()
// NaN
Para solucionar este problema tenemos los callbacks, que nos ayudan a esperar, si nuestra información depende de ese resultado. Los callbacks por convención son una función que recibe un parámetro de error y uno de éxito. Aquí podemos observar lo primero que se va a imprimir es el mensaje Another function y luego el resultado de la suma, esto se debe a que el event loop de nodejs procesa el setTimeOut en una lugar aislado en memoria hasta que finalice y continua con la siguiente linea que es una tarea síncrona
// función que tarda en ejecutarse
function fetchData (callback) {
setTimeout(() => {
// function(error, data)
callback(null, 2);
}, 1000);
}
function processData () {
fetchData(function (err, data) {
data += 1;
console.log(data);
});
console.log("Another function");
}
processData()
// Another function
// 3
Como funcionan los Frameworks de nodeJs
Todos los frameworks están diseñados para tomar ventaja de los callback, desde expressJs que utiliza el callback next()
hasta nestjs que utiliza async/await
una forma más clara de hacer un callback.
app.get('/', function (req, res) {
res.send('Hello World!');
});
//app.get(path, callback){
}
Esta es la manera es que se ven los middleware sin usar use
en express. Podemos pensar en usar un middleware que imprima logs cada vez que se hace una petición, como winston o pino y agregue la fecha en que se ejecutó el request.
// myLogger(param1, param2, callback)
var myLogger = function (req, res, next) {
// Se agrega datos al request
req.requestTime = Date.now();
console.log('LOGGED');
next();
};
const server = http.createServer((req, res) => {
// agrega una propiedad a req y ejecuta un console
myLogger(req, res, function() {
console.log(`RequestTime:${Date(req.requestTime)}`);
...
});
}):
Para finalizar usualmente usamos callbacks con los eventos tanto que nos provee nodejs con cada uno de sus módulos y con los que nosotros mismos creamos:
var example_emitter = new (require('events').EventEmitter);
example_emitter.on("test", function () { console.log("test"); });
example_emitter.emit("test");
Closures
Otro concepto importante en especial cuando anidamos callbacks son los closures que en resumen nos dicen que una función definida dentro de otra función puede tener acceso a las propiedades del padre.
function init() {
var name = 'Mozilla'; // name is a local variable created by init
function displayName() { // displayName() is the inner function, a closure
alert(name); // use variable declared in the parent function
}
displayName();
}
init();
Este concepto se ve mucho si usamos middlewares en express, ya que tanto req como res, se comparte en cada uno de los callbacks que se anidan y es por eso que se pueden agregar propiedades y métodos. En el siguiente ejemplo vemos como myLogger modifica la data de la función salutation, agregado el elemento admin.
var myLogger = function (data, callback) {
data.admin = false;
console.log("my context is: " + this.name);
callback()
};
function salutation() {
let data = {
user: "user",
pass: "password"
}
this.name = "salutation";
myLogger(data, function () {
console.log(data)
})
}
salutation();