#100 sólo para ti (doy la introducción a JS por conocida ):
Introducción rápida a Node.JS
Para empezar no hay mucho más que hacer que instalar el último binario de http://nodejs.org/ (o de tu package manager favorito si estás en Linux, aunque suelen tener builds viejas). El binario de Node viene con NPM, una utilidad de manejo de paquetes propia.
Para ejecutar tanto Node como NPM hay que tirar de consola. Obviamente recomiendo un buen resaltado de sintaxis para tu editor favorito (cualquiera de JS vale). Ejecutando node
se abrirá el REPL o intérprete interactivo. node archivo.js
ejecuta el archivo indicado.
NPM
NPM es el gestor de paquetes de Node. Los proyectos en Node.JS vienen acompañados de un archivo package.json (doc, ejemplo) en el que declaran, entre otras cosas, las dependencias necesarias. Con un simple npm install
dentro de cualquier proyecto Node se descargarán e instalarán automáticamente las dependencias listadas.
Para instalar un paquete a mano, npm install nombrepaquete
(se instalará sólo para la carpeta/proyecto actual).
Para instalar un paquete globalmente, npm install -g nombrepaquete
(necesario privilegios de root/administrador). Por ejemplo, tras instalar CoffeeScript con npm install -g coffee-script
se pueden ejecutar archivos .coffee con coffee archivo.coffee
en cualquier carpeta del sistema.
require
Node añade muy poco sobre el JavaScript básico que ofrece V8 (el engine JS de Google Chrome). Además de la API básica que viene con Node (api docs), se añade una función global require
para cargar paquetes (una especie de #include
de C o import
de Java/Python). La principal diferencia es que require
cachea los accesos por lo que varios require
en la misma ejecución cargarán la misma referencia de paquete (y por tanto, sólo se ejecutará el código global de paquete una vez).
Una vez instalado el paquete ejemplo desde NPM, cargarlo es tan fácil como var example = require('ejemplo');
. El paquete ejemplo estará disponible en la variable example
y se podrá usar como example.propiedad = ...;
require
también admite rutas relativas que te permiten dividir en módulos tu paquete, como por ejemplo require('./modulo');
para cargar el archivo modulo.js de la carpeta actual.
Callbacks
La filosofía de Node se basa en que, siendo este un proceso de un sólo hilo, el proceso (la CPU) debe estar el mínimo tiempo ocupado por el programa para así poder atender más peticiones. Node sólo puede estar ejecutando un código en paralelo (no dispone de hilos) por lo que sólo habrá una petición ejecutándose en CPU al mismo tiempo (a no ser que se lancen varios procesos de Node, independientes unos de otros).
Esto lo hace ideal para programas intensivos en entrada/salida como servidores, donde la CPU no queda bloqueada mucho (debe ser rápida en servir) lo que en teoría beneficia la escalabilidad, aunque por contra lo hace horrible para procesos intensivos en CPU.
¿Cómo funciona entonces Node? ¿Cómo se consigue que los programas no se bloquéen? Node.js añade al modelo de ejecución de JavaScript un sistema de eventos. El modelo de ejecución es algo así:
1. Ejecutar código JS
2. ¿Hay algún listener a la escucha de eventos? FALSE = finalizar programa
3. ¿Hay algún evento a la escucha? TRUE = despachar UN evento en espera
4. Goto 2
Por lo tanto la manera básica de trabajar en Node es asociando listeners a eventos (patrón observador de toda la vida) y así funcionarán el 99% de funciones (salvo que se indique lo contrario): "cuando me llegue una petición HTTP a /index.html haz X", "cuando me llegue una petición HTTP a /room.html haz Y". Tú defines unos eventos y una función (o callback) que será llamada cuando un evento se dispare.
En el JS de nuestros navegadores seguro que más de uno lo ha usado sin saberlo:
setInterval(function() {
console.log("¡Callback llamado!");
}, 3000);
Como ejemplo básico, vamos a usar la función readdir
del paquete fs
para leer los ficheros en un directorio. Entrando en la documentación de fs.readdir vemos que acepta dos argumentos: la ruta y un callback (una función) con los argumentos (err, files)
.
PROTIP: todos los callbacks que pueden resultar en un error recibirán como primer argumento err
que será undefined/null/false si no hay error u otro si ha habido error (usualmente un string con la explicación del error).
var fs = require('fs'); // Cargamos el módulo FS
fs.readdir('./', function(err, files) {
// El readdir ha terminado, estamos dentro del callback
if (err) {
console.log('Error: ' + err);
return; // Error, salgamos del callback
}
console.log('Archivos: ' + files);
});
console.log('Hello files!');
Este programa, una vez ejecutado, imprimirá (si no hay error):
Hello files!
Archivos: ejemplo.js, etc.
¿¡WTF!? ¡Si el console.log de "Hello files!" está DESPUÉS del listado de archivos! En efecto, como comentaba antes, node es por defecto asíncrono/non-blocking, por lo que llamar a fs.readdir no ejecutará el código de forma instantánea, sino que continuará la ejecución normalmente y, cuando el directorio haya sido leído completamente se lanzará el evento y, una vez disparado este, se llamará al callback correspondiente.
PROTIP: los callbacks no tienen por qué ser anónimos, se podría haber hecho:
function callback(err, files) {
if (err) {
console.log('Error: ' + err);
return; // Error, salgamos del callback
}
console.log('Archivos: ' + files);
};
fs.readdir('./', callback);
o
var callback = function(err, files) {
if (err) {
console.log('Error: ' + err);
return; // Error, salgamos del callback
}
console.log('Archivos: ' + files);
};
fs.readdir('./', callback);
Práctica que, de hecho, es recomendable cuando se empieza a enmarañar el código.
Servidor HTTP
Como código de ejemplo el típico servidor HTTP simplote:
var http = require('http');
var HOST = '127.0.0.1';
var PORT = 1337;
var server = http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
});
server.listen(PORT, HOST);
console.log('Server running at http://' + HOST+ ':' + PORT+ '/');
Ejecutar en Node y acceder a http://127.0.0.1:1337 (o el HOST/PORT indicado). Así de sencillo.
Enlaces
Naaaaah, ¡usad Google!