Introducción:

Últimamente he estado probando y experimentando con algunas tecnologías interesantes: virtualización a nivel de sistema operativo: chroot / lxc / docker. He estado experimentando también con heroku y monhohq. Y hace tiempo estuve experimentando con node-webkit y cross-walk y traceur compiler.

Este post lo escribo a poco de que salga nodejs 0.12, y typescript 1.0 y tras haberle dado otra oportunidad a dart en su versión 1.1 con muchas expectativas y con un resultado algo decepcionante. Con generadores implementados en v8 y disponibles en node.js a partir de la 0.11, y con task.js y q de por medio, habiendo adquirido mucha más experiencia con promesas, señales y streams, y con las promesas ya propuestas en el draft de harmony.

Promesas:

Ya he hablado mucho en otras ocasiones sobre promesas/futures y no voy a insistir. Aunque para aprovechar este post conviene conocer las promesas y aceptar que son una mejor solución a la gestión de flujos asíncronos:

Promesas > callbacks/continuaciones > events/error+success

Aquí hay una presentación que explica bastante bien todo esto.

http://www.slideshare.net/domenicdenicola/callbacks-promises-and-coroutines-oh-my-the-evolution-of-asynchronicity-in-javascript](http://www.slideshare.net/domenicdenicola/callbacks-promises-and-coroutines-oh-my-the-evolution-of-asynchronicity-in-javascript)
También he hablado ya sobre esto en otros posts, aunque ahora entraré más en detalle de cómo está implementado y he creado una demo que he subido a github y que se puede colocar en heroku.
http://blog.cballesterosvelasco.es/2013/06/haxe3-starling-continuation-air.html
http://blog.cballesterosvelasco.es/2013/05/promesas.html
http://blog.cballesterosvelasco.es/2013/05/generadores.html
http://blog.cballesterosvelasco.es/2013/05/async-codigo-asincrono-lineal-en.html

Y ya puestos:

Event loop + Workers/Isolates > fibers > threads > process

Promesas + generadores = asíncrono lineal/secuencial:

Con lenguajes que soportan generadores bidireccionales, como las últimas versiones de javascript y de php, se puede conseguir escribir código asíncrono como si fuese código síncrono como el await/async de .NET . Básicamente task.js.

Aquí hice una pequeña implementación en PHP:

https://github.com/soywiz/asynctaskphp/blob/master/asynctaskphp.php

https://github.com/soywiz/asynctaskphp/blob/master/test.php

El núcleo de esta idea:

function spawn($functionGenerator) {
    $deferred = Promise::createDeferred();
    $generator = $functionGenerator();
    $step = function() use ($generator, &$step, $deferred) {
        $promise = $generator->current();

        if ($promise instanceof IPromise) {
            $promise->then(function($result, $error) use ($generator, &$step) {
                if ($error != null) {
                    $generator->throw($error);
                } else {
                    $generator->send($result);
                }
                $step();
            });
        } else {
            if (!$generator->valid()) {
                $deferred->resolve();
            }
        }
    };

    $step();
    return $deferred->promise;
}

¿Cómo usar generadores con q para hacer asíncrono lineal a lo task.js usando typescript y con autocompletado gracias a genéricos?

A partir de node 0.11 haciendo uso del switch –harmony, es posible utilizar generadores y por ende escribir código asíncrono de forma lineal y con todas las ventajas de gestión de errores, de evitar pirámides de la muerte, etc. etc. comentados en la presentación de slideshare que he referenciado antes.

Y eso está muy bien, pero en mi caso solo me sirve si lo puedo usar con typescript. Y typescript no soporta la sintaxis function* que es necesaria para los generadores. Por todo lo demás typescript es cojonudo y hay un pequeño truquito que estoy utilizando para poder tener esto en typescript.

Consiste en tener un pequeño javascript que se ejecuta siempre al iniciar la aplicación, que se recorre todas las carpetas en busca de archivos .js y busca la secuencia “Q.spawn(function(“ y la reemplaza por “Q.spawn(function*(“ y utilizar yield como si fuese una función en vez de una estructura de control. Es un pequeño apaño y sería mucho más apropiado tener un parser que parsee todos los js en busca de funciones con yield y que las reemplace por *. Pero es suficiente para mí, es sencillo y funciona bien. Ese js debe ser un bootstrap que arranque luego la aplicación.

También hace falta “parchear” la definición de Q, añadiendo:

declare function yield<T>(promise:__Q.Promise<T>):T;
export function spawn<T>(generatorFunction: any):void;

Básicamente engañamos a typescript y le decimos que hay una función llamada yield y que lo que hace es transformar una Promise a T. Que es precisamente lo que hace el yield en los generadores pasados al spawn; con lo que podemos tener autocompletado.

{
  "name": "heroku-typescript-generators-sample",
  "version": "0.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node --harmony bootstrap.js",</
    "test": "node_modules/.bin/mocha --harmony --ui exports -R spec -r updateasync --globals name "</
  },
  "dependencies": {
    "underscore": "^1.6.0",
    "mongodb": "^1.3.23",
    "websocket": "^1.0.8",
    "connect": "^2.14.1",
    "urlrouter": "^0.5.4",
    "atpl": "^0.7.6",
    "q": "^1.0.0"
  },
  "devDependencies": {
    "mocha": "^1.17.1"
  },
  "engines": {
    "node": "^0.11.11"
  }
}

Siendo

require('./updateasync.js');
require('./index.js');

Y updateasync.js:

var fs = require('fs');

function handleFolder(folder) {
    fs.readdirSync(folder).forEach(function (fileName) {
        var fullPath = folder + '/' + fileName;
        if (fileName == 'node_modules') return;
        if (fileName.match(/\.js$/)) {
            var data = (fs.readFileSync(fullPath, 'utf-8'));
            var datamod = data.replace(/Q.spawn\s*\(\s*function\s*\(/g, 'Q.spawn(function*(');
            if (data != datamod) {
                fs.writeFileSync(fullPath, datamod, 'utf-8');
                console.log('Fixed generators in "' + fullPath + '"');
            }
        }
        if (fs.lstatSync(fullPath).isDirectory())
            handleFolder(fullPath);
    });
}

handleFolder('.');

Typings / archivos d.ts, definiciones:

Desde que estuve activamente con TypeScript por última vez, todo el tema de conseguir typings para hacer ciertas cosas era bastante manual. Ahora hay un proyecto llamado “tsc” que automatiza bastante todo esto. También ha aumentado bastante desde entonces la colección de definiciones públicos que ha ido haciendo la gente.

IDEs:

Desde la última vez que probé han salido varios entornos para desarrollar con typescript. Incluyendo intelliJ, aunque el Visual Studio sigue siendo el mejor para trabajar con typescript hasta que intelliJ mejore el autocompletado y el parseo en general.

Demo:

Como ya puse en otro post anterior. En cuanto hay algo más de lógica, el uso de generadores se hace cada vez más notable. Especialmente cuando hay diversas estructuras de control y/o bucles.

En otro post escribiré sobre mis experiencias con cross-walk y con traceur para poder escribir este código tan conciso también en el cliente y que funcione en los navegadores actuales / plataformas móviles que todavía no han implementado/activado los generadores en javascript.