Olá pessoal. Tudo certo!?
No post de hoje, apresento alguns conceitos fundamentais para utilização de Rx com Javascript.
Rx Framework foi desenvolvido, inicialmente, para .NET e Silverlight. Mais tarde, foi portado para Javascript. Com ele, podemos simplificar bastante a implementação de rotinas assíncronas, além de poder combinar (compose) eventos.
Se desejar ver alguns exemplos desse framework com .NET, dê uma olhada nos outros posts que escrevi sobre o tema.
Onde obter RxJS?
Atualmente, RxJS pode ser obtido em http://msdn.microsoft.com/en-us/data/gg577610. Nesse download, estão disponíveis, além da biblioteca propriamente dita, um pacote de exemplos e extensões para os principais frameworks para JS. Entre eles: JQuery, mootools e extJS.
Por que utilizar RxJS?
Tentando manter a simplicidade:
- Há uma necessidade crescente de suportar callbacks em nossos códigos (programação assíncrona);
- Há modelos e cenários diversificados para implementação de programação assíncrona (DOM Events, JQuery, AJAX);
- O código pode ficar potencialmente mais limpo através de técnicas como unification e composition.
Recapitulando RxJS
Sempre que estivermos falando em RxJS, teremos dois componentes a considerar:
- Um (ou mais) objeto(s) observable, que emite notificações (ex: lança eventos);
- Um (ou mais) objeto(s) observer, que monitora(m) as notificações emitidas (push) pelo observable.
O objeto observer informa o objeto observable que deseja ser notificado, através de uma “incrição” (subscribe).
As notificações enviadas pelo observable são recebidas no objeto observer a partir de três funções simples:
- onNext – que será acionada sempre que o observable emitir uma notificação;
- onError – que será acionada se houver alguma exception – nesse caso, não vão ocorrer notificações futuras;
- onCompleted – que será acionada quando o observer entender que não há mais notificações a disparar.
Veja esse exemplo simples (html completo, em https://gist.github.com/1383995)
$(function () {
    var observable = Rx.Observable.Range(0, 10);
            
    var observer = observable.Subscribe(
        function (e) { document.write("onNext: " + e + "
"); }, /* onNext */
        function (e) { document.write("onError: " + e + "
"); }, /* onError */
        function () { document.write("OnCompleted") }               /* onCompleted */
    );
    observer.Dispose();
});
              Que resulta em:
Como pode perceber:
- começo criando um objeto observable através de um recurso do próprio framework;
- faço a “incrição” (subscribe) gerando um objeto observer;
- descarto o objeto observer (o que cancela a assinatura).
Durante a execução, a função onNext é acionada uma vez para cada um dos elementos do intervalo. No fim, a função onCompleted é chamada.
Importante destacar que as funções onError e onCompleted são opcionais.
Built-in Observable Sequences Factory Functions
No código que demonstrei acima, utilizei uma Observable Sequence Factory Functions. Há algumas outras. Perceba:
Range
Gera uma chamada para a função onNext para cada elemento da enumeração.
var source = Rx.Observable.Range(0, 10);
source.Subscribe(function(x) { /* 0..9 */ });
              Return
Gera uma única chamada para a função onNext.
var source = Rx.Observable.Return("RxJS");
source.Subscribe(function(s) { /* recebe“RxJS” */ });
              FromArray
Gera uma chamada para cada elemento do array passado como parâmetro para a factory function.
var source = Rx.Observable.FromArray(["a", "b", "c"]);
source.Subscribe(function(s) { /* ‘a’..‘c’ */ });
              Timer
Gera uma chamada sempre que um intervalo de tempo for acionado.
var observable = Rx.Observable.Timer(1000, 2000);
var observer = observable.Subscribe(
    function (e) { $("#target").text(e); }/* onNext */
);
              Repare que, nesse exemplo, a primeira chamada acontece após 1 segundo. As demais, em intervalos de dois segundos.
Usando Observables como consultas
Por que usar Rx para “assinar” eventos, afinal?! Porque podemos usar uma abordagem semelhante a consultas LINQ. Observe:
var o = Rx.Observable.Range(0, 50)
    .Where(function (x) { return x % 2 === 0 });
              Primeiros 4:
var o = Rx.Observable.Range(0, 50)
    .Where(function (x) { return x % 2 === 0 })
    .Take(4);
              Últimos 4:
var o = Rx.Observable.Range(0, 50)
    .Where(function (x) { return x % 2 === 0 })
    .TakeLast(4);
              Ignorando os primeiros 5 elementos:
var o = Rx.Observable.Range(0, 50)
    .Where(function (x) { return x % 2 === 0 })
    .Skip(5);
              E por aí, vai 
Convertendo eventos em coleções Observables
RxJS possui suporte extendido a eventos. Aliás, essa característca levou o Rx original a ser chamado LINQ to Events.
Podemos “assinar” eventos usando DOM puro. Observe (html completo em https://gist.github.com/1384156).
Rx.Observable.FromHtmlEvent(window, "load").Subscribe(function () {
    var canvas = document.getElementById("myCanvas");
    var mousemove = Rx.Observable.FromDOMEvent(canvas, "mousemove");
    mousemove.Subscribe(function (e) {
        document.getElementById("target").innerHTML = e.clientX + ", " + e.clientY;
    });
});
              Ou podemos usar JQuery (html completo em https://gist.github.com/1384173).
$(function () {
    var mousemove = $("#myCanvas").toObservable("mousemove");
    mousemove.Subscribe(function (e) {
        $("#target").text(e.clientX + ", " + e.clientY);
    });
});
              Composição de eventos
A partir do momento que conseguimos “escutar” eventos como consultas, podemos trabalhar composição. Observe (html completo em https://gist.github.com/1384388):
$(function () {
    var mouseMove = $(document).toObservable("mousemove");
    var mouseDown = $(document).toObservable("mousedown");
    var mouseUp = $(document).toObservable("mouseup");
    var canvas = $("#target");
    var context = canvas.get(0).getContext('2d');
    function getCoords(e) {
        if (e.offsetX)
            return { x: e.offsetX, y: e.offsetY };
        return { x: e.pageX - canvas.get(0).offsetLeft, y: e.pageY - canvas.get(0).offsetTop };
    }
    var mouseMoveWithButtonDown = mouseDown.SelectMany(function (down) {
        return mouseMove
            .Select(function (move) {
                return {
                    start: getCoords(down),
                    current: getCoords(move)
                }
            })
            .TakeUntil(mouseUp)
    }
    );
    mouseMoveWithButtonDown.Subscribe(function (e) {
        context.beginPath();
        context.moveTo(e.start.x, e.start.y);
        context.lineTo(e.current.x, e.current.y);
        context.stroke();
    });
});
              Repare a composição mouseMoveWithButtonDown. Na prática, estou monitorando os movimentos do mouse, a partir de um “mousedown”, até que ocorra um “mouseup”. Além disso, estou “montando” um registro mais interessante para nosso contexto, com a posição onde o mouse foi pressionado e a posição atual do ponteiro.
O live demo está aqui: http://users.cjb.net/livedemoelemarjr/rx_mouse.htm
Composição de eventos – algo mais avançado
Já conseguimos compor eventos. Agora, para mostrar o potencial do framework, vamos fazer um registro de uma ocorrência anterior e usar como “input” para uma ocorrência atual. Observe (https://gist.github.com/1384596):
$(function () {
    var mouseMove = $(document).toObservable("mousemove");
    var mouseDown = $(document).toObservable("mousedown");
    var mouseUp = $(document).toObservable("mouseup");
    var canvas = $("#target");
    var context = canvas.get(0).getContext('2d');
    function getCoords(e) {
        if (e.offsetX)
            return { x: e.offsetX, y: e.offsetY };
        return { x: e.pageX - canvas.get(0).offsetLeft, y: e.pageY - canvas.get(0).offsetTop };
    }
    var mouseMoves = mouseMove
        .Skip(1)
        .Zip(mouseMove, function (left, right) {
            return {
                before: getCoords(left),
                current: getCoords(right)
            };
        });
    var mouseMoveWithButtonDown = mouseDown.SelectMany(function (down) {
        return mouseMoves
            .Select(function (r) {
                return {
                    start: getCoords(down),
                    before: r.before,
                    current: r.current
                }
            })
            .TakeUntil(mouseUp)
    }
    );
    mouseMoveWithButtonDown.Subscribe(function (e) {
        context.beginPath();
        context.strokeStyle = "#f00";
        context.moveTo(e.start.x, e.start.y);
        context.lineTo(e.current.x, e.current.y);
        context.stroke();
        context.beginPath();
        context.strokeStyle = "#00f";
        context.moveTo(e.before.x, e.before.y);
        context.lineTo(e.current.x, e.current.y);
        context.stroke();
                
    });
});
              O que fizemos?!
- Criamos uma composição para “zipar” duas ocorrências de mouseMove;
- Combinamos as ocorrências em um registro com o “ponto anterior” e com o “ponto atual”;
- Desenho um contorno.
Live demo: http://users.cjb.net/livedemoelemarjr/rx_mouse2.htm
Há muito mais possibilidades. Mas, por hoje é isso!





 
          

 
          
novembro 26th, 2011 → 20:38
[...] Se você não sabe nada sobre RxJS, considere ler Reactive Extensions for Javascript (RxJS Framework). [...]