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). [...]