Reactive Extensions - Javascript (RX JS)

  1. Rx Js – Genel Bilgiler
  2. Rx Js – Genel Kullanım
  3. Rx Js – AutoSuggest TextBox İzinde
  4. Rx Js – Ajax
  5. Rx Js – Sonuç
  6. Referanslar

1.     Rx Js – Genel Bilgiler

Reaktif programlama modeli Microsoft’un yeni Reactive Extensions .Net kütüphanesi ile hayatımıza girmiş bulunuyor. Temelinde Observer pattern’ini kullanan bu model dilden ve platformdan bağımsızdır. Nitekim, Java, Objective-C,  C++, C#’da bu patterni rahatlıkla uygulayabilmekteyiz. Javascript dilinde de Observer tasarım kalıbı uygulanabilmektedir. Hatta Reactive Extensions kütüphanesinin bir benzeri de Javascript için bulunmaktadır. Bu Javascript kütüphanesi internette RxJs olarak tanınmaktadır.

RxJs kütüphanesi .Net için yapılan Rx kütüphanesi ile çok benzer bir kullanıma sahip. Detaylara değineceğiz, ancak RxJs’in tarihsel sürecinden de bahsetmemiz faydalı olacaktır. Rx projesi ilk olarak GitHub üzerinde host edilmiş ve daha sonra Codeplex aracılığıyla Microsoft bünyesine katılmıştır. RxJs ise Rx ile birlikte paralel olarak geliştirilen bir kütüphanedir. Yine ilgili linklerde Observer tasarım kalıbının diğer yazılım dillerinde nasıl uygulandığını gösteren örnekler mevcuttur. İncelemenizi tavsiye ederim.

RxJs kütüphanesi, önceki paragrafta bahsettiğim üzere hem GitHub hem de Codeplex üzerinde bulunmaktadır. Tutorial ve videoların bulunduğu MSDN sayfasını da incelemek isteyebilirsiniz.

RxJs bir Javascript kütüphanesi olduğunu unutmamalıyız. Bu durumda Javascript’in kısıtlamaları ya da özgürlükleri bu kütüphane için de geçerlidir. C# ile karşılaştırmalı olarak ilerlemenin faydalı olacağını düşünüyorum.

Not: Rx Js toplamda 8 Javascript kütüphanesinden oluşmaktadır ve herbirini NuGet üzerinden ayrı ayrı projenize yükleyebilirsiniz. Bu kütüphaneler aşağıda listelenmiştir;

Rx Js Kütüphane Adı

Dosya adı

Açıklama

Main

rx.js

Observer tasarım kalıbını hayata geçirebileceğiniz yapılar bulunmaktadır. Diğer tüm kütüphaneler buradaki yapılara ek fonksyonlar kazandırmaktadır.

Aggregation Operations

rx.aggregates.js

Observable nesneler için gruplama özellikleri kazandırmaktadır. sum, count, any, all v.b.

Binding

rx.binding.js

Hot Observable nesneler oluşturmak için kullanılan kütüphane.

Coincidence

rx.coincidence.js

Observable’larda kullanılan Join, Buffer türevli ek fonksyonlar.

Join Patterns

rx.joinpatterns.js

Observable’ları kesiştirmeye yarayan When, Then, And türevli ek fonksyonlar.

Time-Based Operations

rx.time.js

Zamana bağlı işlemler için Observable’lar üzerinde tanımlanan fonksyonlar.

Experimental

rx.experimental.js

Rx Js içerisindeki deneme amaçlı eklenmiş ve test aşamasında olan özellikler.

Test Library

rx.testing.js

Rx Js kullanılarak yazılmış kodların testi için kullanılabilecek yardımcı yapılar sunmaktadır.

 

Bunlardan sadece Main kütüphanesi zorunludur, diğerleri opsiyoneldir. Bunların yanında eğer kullanım açısından kolaylaık sağlayan Rx Js’in uyumluluğu (entegrasyonu) için geliştirilmiş çeştli kütüphaneleri kullanabilirsiniz. Örneğin uygulamanızda zaten jQuery kullanıyorsanız Rx Js kütüphanesi ile uyumluluğu sağlayan Rx Js-jQuery kütüphanesini de kullanmak çok faydalı olacaktır. Rx Js’in uyumluluk sağladığı diğer kütüphaneleri aşağıda listelenmiş olarak bulabilirsiniz;

Rx Js Plug-In

Dosya adı

Açıklama

RxJs-HTML

rx.html.js

HTML DOM için uyumluluk.

RxJs-Node

rx.node.js

Node Js için uyumluluk.

RxJs-Ext JS

rx.extjs.js

Ext JS için uyumluluk.

RxJs-jQuery

rx.jquery.js

jQuery için uyumluluk

RxJs-MooTools

rx.mootools.js

MooTools için uyumluluk.

RxJS-DoJo

rx.dojo.js

DoJo için uyumluluk.

RxJs-WinJS

rx.winjs.js

Windows 8 ve WinJS için uyumluluk.

 

2.     Rx Js – Genel Kullanım

Rx Js kullanabilmek için projenize Main kütüphanesini ekleyin. Ardından aşağıdaki gibi bir sayfa oluşturun;

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title></title>

    <script type="text/javascript" src="../Scripts/RX/rx.js"></script>

    <script type="text/javascript">

        (function (window, undefined) {

 

            Rx.Observable.fromHTMLEvent = function (element, eventName) {

                return Rx.Observable.create(function (observer) {

                    var observer_onNext = function (data) { observer.onNext(data); };

                    element.addEventListener(eventName, observer_onNext, false);

                    return Rx.Disposable.create(function () {

                        element.removeEventListener(eventName, observer_onNext);

                    });

                });

            };

 

            window.addEventListener("load", function window_load(loadEvent) {

                window.removeEventListener("load", window_load, false);

 

                var KeyUpEvent = Rx.Observable.fromHTMLEvent(WikipediaSearch, "keyup");

                var KeyUpEventSubscriber = KeyUpEvent.subscribe(

                     function (eventData) {

                         window.alert("KeyUpEvent gerçekleşti. Değer: " + eventData.srcElement.value);

                     }, function (error) {

                         window.alert("İstenmeyen bir hata oluştu: " + error);

                     }, function () {

                         window.alert("KeyUpEvent sonlandırıldı. Haberdar olmak için subscribe olun.");

                     }

                );

 

            }, false);

 

        } (this));

    </script>

</head>

<body>

    <table>

        <tr>

            <td><input type="text" id="WikipediaSearch" /></td>

        </tr>

        <tr>

            <td><div id="WikipediaSearch_Results"></div></td>

            <td><div id="WikipediaSearch_Logs"></div></td>

        </tr>

    </table>

</body>

</html>

 

 

Bu verilen örnekte bir TextBox kontrolünün KeyUp olayını temsil eden KeyUpEvent adında bir Observable yaratılıyor.

Observable (TextBox KeyUp Event)

Rx C#

var KeyUpEvent = Observable.FromEventPattern<KeyEventArgs>(WikipediaSerach, "KeyUp");

Rx Js

var KeyUpEvent = Rx.Observable.fromHTMLEvent (WikipediaSearch, "keyup");

// veya

var KeyUpEvent = Rx.Observable.fromJQueryEvent (WikipediaSearch, "keyup");

 

 Buradaki “fromHTMLEvent” ile “fromJQueryEvent” fonksyonlarını biz yazmış olalım: Bu fonksyonlar aşağıdaki gibidir;

// IE 9 ve genel olarak HTML 5 destekli tüm tarayıcılarda çalışmaktadır.

Rx.Observable.fromHTMLEvent = function (element, eventName) {

return Rx.Observable.create(function (observer) {

// Aşağıdaki satır yerine doğrudan observer.onNext de kullanılabilirdi ancak RxJs kütüphanesi hata veriyor.

var observer_onNext = function (data) { observer.onNext(data); };

element.addEventListener(eventName, observer_onNext, false);

              return Rx.Disposable.create(function () {

element.removeEventListener(eventName, observer_onNext);

});

});

};

// Eğer jQuery kullanılıyorsa çalışmaktadır

Rx.Observable.fromJQueryEvent = function (element, eventName) {

return Rx.Observable.create(function (observer) {

// Aşağıdaki satır yerine doğrudan observer.onNext de kullanılabilirdi ancak RxJs kütüphanesi hata veriyor.

var observer_onNext = function (data) { observer.onNext(data); };

$(element).bind(eventName, observer.onNext);

              return Rx.Disposable.create(function () {

$(element).unbind(eventName, observer.onNext);

              });

       });

};

 

Şimdi yarattığımız KeyUpEvent olayından haberdar olmamız için Observer yaratmaya sıra geldi.

 Gerek C# ‘daki gerekse Javascript’deki kullanımlarda OnNext metodunun alacağı parametre oldukça belirsizdir. Örneğin aşağıdaki Rx C# için verilen Observer örneğinde kullanılan “eventData” parametresi ne olduğu belli olmayan bir olgudur. C#’da genericler sayesinde type güvenliği sağlanabilmektedir: Observer nesnesini KeyEventArgs nesnesi yardımıyla yarattığımız için buradaki eventData parametresi EventPattern<KeyEventArgs> türünden bir nesne olacaktır. Ancak bu durum Rx Js için geçerli değildir:

Observer (TextBox KeyUp Event)

Rx C#

var KeyUpEventSubscriber = KeyUpEvent.Subscribe(

     eventData => {

          MessageBox.Show("KeyUpEvent gerçekleşti. Değer: " + ((TextBox)eventData.Sender).Text);

     }, error => {

          MessageBox.Show("İstenmeyen bir hata oluştu: " + error.ToString());

     }, () => {

          MessageBox.Show("KeyUpEvent sonlandırıldı. Haberdar olmak için subscribe olun.");

     }

);

Rx Js

var KeyUpEventSubscriber = KeyUpEvent.subscribe(

     function (eventData) {

          window.alert("KeyUpEvent gerçekleşti. Değer: " + eventData.srcElement.value);

     }, function (error) {

          window.alert("İstenmeyen bir hata oluştu: " + error);

     }, function () {

          window.alert("KeyUpEvent sonlandırıldı. Haberdar olmak için subscribe olun.");

     }

);

 

Dikkat edilirse WikipediaSerach kontrolünün text değeri Javascript tarafında “eventData.srcElement.value”  ile alınmıştır. Eğer Rx.Observable.fromJQueryEvent fonksyonunu kullanmışsak onNext fonksyonunda elde edeceğimiz eventData nesnesi jQuery’nin Event nesnesi olacaktır. Bu nedenle eğer jQuery kullanmışsak değeri “$(eventData.target).val()” ile de alabiliriz.

Okuyucu herhalde bu kadar kullanışlı bir fonksyonun neden Rx Js’e dahil edilmediğini soracaktır. Bu fonksyon Rx Js’de bulunmaktadır ancak mantığını kavrayabilmek açısından yukarıdaki örnek üzerinden gidilmiştir. Geliştirme yaparken bu tarz ek fonksyonlar yapmak yerine kullanımınıza uygun uyumluluk kütüphanelerinde birini seçmenizi öneririm; bu kütüphaneler cross-browser çalışabilmektedir. Bu durumda projemizde hiç bir kütüphane kullanmayıp sadece HTML DOM üzerinde çalışıyorsak RxJs-HTML kütüphanesi kullanmak hem yazacağınız kodu azaltacak/düzenleyecektir hem de uygulamanıza Cross-Browser desteği de getirecektir. Bu durumda yukarıda açıkladığımız örnek uygulamamız aşağıdaki gibi olacaktır;

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title></title>

    <script type="text/javascript" src="../Scripts/RX/rx.js"></script>

    <script type="text/javascript" src="../Scripts/RX/integration/rx.html.js"></script>

    <script type="text/javascript">

        (function (window, undefined) {

 

            window.addEventListener("load", function window_load(loadEvent) {

                window.removeEventListener("load", window_load, false);

 

                var KeyUpEvent = Rx.Observable.fromEvent(WikipediaSearch, "keyup");

                var KeyUpEventSubscriber = KeyUpEvent.subscribe(

                     function (eventData) {

                         window.alert("KeyUpEvent gerçekleşti. Değer: " + eventData.srcElement.value);

                     }, function (error) {

                         window.alert("İstenmeyen bir hata oluştu: " + error);

                     }, function () {

                         window.alert("KeyUpEvent sonlandırıldı. Haberdar olmak için subscribe olun.");

                     }

                );

 

            }, false);

 

        }(this));

    </script>

</head>

<body>

    <table>

        <tr>

            <td><input type="text" id="WikipediaSearch" /></td>

        </tr>

        <tr>

            <td><div id="WikipediaSearch_Results"></div></td>

            <td><div id="WikipediaSearch_Logs"></div></td>

        </tr>

    </table>

</body>

</html>

 

Aynı örneğin jQuery ve RxJs-jQuery kütüphaneleri kullanılarak yapılmış hali aşağıdaki gibi olacaktır;

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title></title>

    <script type="text/javascript" src="../Scripts/jQuery/jquery-1.8.3.js"></script>

    <script type="text/javascript" src="../Scripts/RX/rx.js"></script>

    <script type="text/javascript" src="../Scripts/RX/integration/rx.jquery.js"></script>

    <script type="text/javascript">

        $(document).ready(function () {

 

            var KeyUpEvent = $(WikipediaSearch).keyupAsObservable();

            var KeyUpEventSubscriber = KeyUpEvent.subscribe(

                 function (eventData) {

                     window.alert("KeyUpEvent gerçekleşti. Değer: " + $(eventData.target).val());

                 }, function (error) {

                     window.alert("İstenmeyen bir hata oluştu: " + error);

                 }, function () {

                     window.alert("KeyUpEvent sonlandırıldı. Haberdar olmak için subscribe olun.");

                 }

            );

 

        });

    </script>

</head>

<body>

    <table>

        <tr>

            <td><input type="text" id="WikipediaSearch" /></td>

        </tr>

        <tr>

            <td><div id="WikipediaSearch_Results"></div></td>

            <td><div id="WikipediaSearch_Logs"></div></td>

        </tr>

    </table>

</body>

</html>

 

Görüldüğü gibi Rx ile Rx Js kütüphanesinin kullanımları neredeyse aynıdır. Rx kütüphanesindeki pekçok özellik Rx Js kütüphanesinde de mevcuttur. Bundan sonraki örneklerimiz sadece Rx Js kullanılarak yapılmıştır. Aşağıdaki örnekte WikipediaSearch textbox’ı içerisine en az 4 ve üstü sayıda karakter bulunduğu zaman mevcut yazıyı WikipediaSearch_Log içerisine yazdıracağız; ve bunun için RxJs-HTML ile RxJs-jQuery uyumluluk kütüphaneleri kullanılacaktır:

// jQuery kullanılmamışsa

var SearchableTextEnteredEvent = Rx.Observable.fromEvent(WikipediaSearch, "keyup")

.select(function (eventData) {

return eventData.srcElement.value;

}).where(function (text) {

return text.length >= 4;

});

var SearchableTextLogger = Rx.Observer.create(

function (text) {

WikipediaSearch_Log.appendChild(document.createTextNode(text));

}, function (error) {

window.alert("İstenmeyen bir hata oluştu: " + error);

       },

undefined

);

SearchableTextEnteredEvent.subscribe(SearchableTextLogger);

// jQuery kullanılmışsa

var SearchableTextEnteredEvent = $(WikipediaSearch).keyupToObservable()

.select(function (eventData) {

return $(eventData.target).val();

}).where(function (text) {

return text.length >= 4;

});

var SearchableTextLogger = Rx.Observer.create(

function (text) {

$(WikipediaSearch_Log).append(text);

}, function (error) {

window.alert("İstenmeyen bir hata oluştu: " + error);

       },

undefined

);

SearchableTextEnteredEvent.subscribe(SearchableTextLogger);

 

Görüldüğü gibi bize kullanıcının girdiği en az 4 karakter uzunluğundaki yazıyı bildiren nesne SearchableTextEnteredEvent ‘dir ve loglama işini üstlenen SearchableTextLogger nesnesi sadece ve sadece kendisini subscribe ederek kendi işini yapabilmektedir.

3.     Rx Js – AutoSuggest TextBox İzinde

Farkettiğiniz üzere amacımız otomatik olarak yazıya ilişkin arama yapan bir textbox yapmak.

Yukarıdaki örneklerin tamamı UI thread’i bloke eden yani senkron (synchronous) kullanımlardır. Aynı Rx’de olduğu gibi Rx Js’de de UI thread’i bloke etmeden ayrı çalışabilen asenkron (asynchronous) kullanımlar mevcuttur. Örneğimizin şu anki haline bakacak olursak, kullanıcı 4 karakter ve fazlasında klavyede her tuşa bastığında birtakım işlemler yapılmaktadır. Bu işlemlerin aslında sunucuya gidecek işlemler olması durumunda sunucumuz oldukça zor anlar yaşayacaktır. 1 kişi saniyede 3 defa bir tuşa basmış olsa ve sitede aynı anda 10 kullanıcı işlem yapıyor olsa sunucumuz 1 sn’de gelen 30 ajax requestini karşılamak zorunda kalacaktır. Bir diğer bakış açısı da servis ücretlendirmeleridir; yine aynı senaryoda UI katmanını yazan biz olursak ve ücretli birtakım servislerden faydalanıyorsak yine saniyede 30 requeste karşılık ödeyeceğimiz fiyat da artacaktır. Her iki durumda da bunun önüne geçmemiz gerekir.

Yapmak istediğimizi bir de Reactive programlama mantığıyla yorumlayalım; Reactive mantığına göre sabit bir anda elimizde EventSource’lar (Observable nesneler) ve Subscriber’lar (Observer nesneler) bulunmakta. Bu EventSource/Subscriber kavramlarını talep/arz  olarak da düşünebilirsiniz. Yukarıda açıkladığımız problemde kullanıcı sürekli klavyeye tuşlarına bastığı için EventSource tarafı doldukça dolmaktadır: burada aşırı talep sözkonusu diye düşünebilirsiniz ancak asıl problem talebin aşırı hızlı olmasıdır. Kullanıcı klavyeye her basışında kendisine ilgili kayıtları getirmesini beklemektedir ancak saniyede 3 tuşa basan bir kişi bu kadar kısa sürede kayıtları okuması mümkün değildir. Aşırı hızlı dememizin sebebi budur. Bu nedenle yapabileceğimiz 2 şey var; talebin hızını azaltmak ya da arzın hızını artırmak. Talep client olacağı için arzı yapan da sunucu olacaktır: Eğer bizim para ödeyerek kullandığımız bir takım servisler, sunucular v.s. bulunuyorsa buna ilişkin kota artırımlarını yapmamız gerekir ve de DOS ataklarına karşı da önlem almamız gerekir. Bu çözüm hosting hizmeti aldığımız firmaya daha fazla para ödememiz gerektiği anlamına gelir (gerçek zamanlı uygulamalar için kabul edilebilirdir). Üstelik sorunun kaynağı sunucunun yavaşlığından ziyade kullanıcının aşırı hızlı olmasındandır. Bu durumda genellikle talebin hızını dizginlemek çok daha akılcı bir çözümdür.

Rx kütüphanelerinde talep(a.k.a. EventSource, a.k.a. Observable) hızı çok çeşitli yöntemlerle düşürülebilmektedir. Örneğin talebi azaltmak da talebin hızını düşürecektir, dolayısıyla önceki örneklerde olduğu gibi “where” fonksyonlarını kullandıkça talep hızını da azaltmaktayız. Bu konuda size “Distinct” ve “DistinctUntilChanged” fonksyonlarını da kullanabileceğinizi önererek size tiyo vermiş olayım (bu konudaki araştırma okuyucuya bırakılmıştır).

(Not: Talep hızı tamamen uydurulmuş bir terim değildir: Gerçekten de Talep Hızı = Talebin önemi / talep etme süresi olarak düşünebilirsiniz. Bu durumda Talep hızını düşürmek  için Talebin önemini azaltmak ya da talep etme süresini arttırmak gerekecektir. Talebin öneminin azalması “uygulamam talebe karşılık verse de olur vermese de” demek gibi oluyor J, bu nedenle fizik kuralları açısından da talep etme süresini artırmak yine yukarıda bahsedildiği gibi akılcı bir çözüm olacaktır.)

Talebin hızını düşürmenin bir diğer yolu da talep etme süresinin azaltılmasıdır. Örneğin bir kullanıcı klavyedeki bir tuşa bastıktan yaklaşık 1 sn sonra karşısında ilgili kayıtları görmek istiyor olsun. Bu durumda klavye tuşuna basıldıktan sonra 1 sn boyunca olayın tetiklenmesi bekletilecektir ve eğer bu süre içerisinde yeniden klavyede tuşa basılırsa bu sefer 1 sn’lik geri sayım yeniden başlayacaktır. Bunu kendimiz yazmak istersek şu şekilde yazardık;

Rx.Observable.prototype.throttle = function (dueTime) {

       var sourceObservable = this;

       return Rx.Observable.create(function (observer) {

var settedTimeout;

return sourceObservable.subscribe(

function (value) {

if (settedTimeout)

window.clearTimeout(settedTimeout);

                           

settedTimeout = window.setTimeout(function () {

observer.onNext(value);

}, dueTime);

}, function (error) {

observer.onError(error);

}, function () {

observer.onCompleted();

}

);

});

};

 

Ancak Rx Js içerisinde, bizim yaptığımız işlemin bir benzerini scheduler denen yapılarla sağlayabiliyoruz. Örneğin timeoutScheduler kullanarak aynı kod daha temiz bir şekilde de yazılabilirdi;

Rx.Observable.prototype.throttle = function (dueTime) {

       var sourceObservable = this;

       return Rx.Observable.create(function (observer) {

             return sourceObservable.subscribe(

                    function (value) {

                           Rx.Scheduler.timeout.scheduleWithRelative(dueTime, function () {

                                  observer.onNext(value)

                           });

                    }, function (error) {

                           observer.onError(error);

                    }, function () {

                           observer.onCompleted();

                    }

             );

       });

};

 

Burada kullanılan Rx.Scheduler.timeout nesnesi window.setTimeout ile window.setInterval fonksyonlarını kendi içinde kullanarak istenen işlemi yapmaktadır. Rx kütüphanesinde farklı Scheduler nesneleri bulunmaktadır. Örneğin Rx.Scheduler.currentThread kullanıldığında textbox içindeki işaretçinin (cursor) ve sayfanın 1 saniye boyunca donduğunu gözlemleyeceksiniz. Bunun sebebi 1 sn boyunca bekleme işlemini Rx.Scheduler.currentThread devralmıştır ve bu Scheduler işlemlerini çalışmakta olan threade yüklemektedir ki bu da UI’ın donmasına sebep olmaktadır. Rx.Scheduler.timeout ise tüm işlerini yeni bir thread içerisinde yapmaktadır bu nedenle UI’da donmaya sebep olmamaktadır. Son olarak Rx.Scheduler.immediate anında çalıştırılmaktadır: dolayısıyla bekletme işlemi için uygun değildir.

Rx Js’de zamana bağlı işlemler için Observable sınıfına yapılan eklentiler Time-Based Operations kütüphanesinde bulunmaktadır. Bu kütüphane içerisinde throttle, delay, timeout ve türevl olan fonksyonlar bulunmaktadır. Yani yukarıda yaptığımız throttle fonksyonunun işlevsel olarak aynısı Rx Js kütüphanesinde de mevcuttur. Dolayısıyla yukarıdaki fonksyonu yazmadan sadece Time-Based Operations kütüphanesini sayfamıza ekleyerek de istediğimizi elde edebiliriz. Şimdi throttle özelliğini nasıl kullandığımızı inceleyelim;

var WikipediaSearch_keyup = Rx.Observable.fromEvent(WikipediaSearch, "keyup");

var SearchableTextEnteredEvent = WikipediaSearch_keyup

    .select(function (eventData) {

        return eventData.srcElement.value;

    }).where(function (text) {

        return text.length >= 4;

    }).throttle(1000);

           

var SearchableTextLogger = Rx.Observer.create(

    function (text) {

        WikipediaSearch_Logs.appendChild(document.createTextNode(text));

        WikipediaSearch_Logs.appendChild(document.createElement("br"));

    }, function (error) {

        window.alert("İstenmeyen bir hata oluştu: " + error);

    }, undefined

);

 

SearchableTextEnteredEvent.subscribe(SearchableTextLogger);

 

Burada işlevsellik olarak uygun olmayan bir durum var: klavyeden CapsLock, Ctrl, Alt, Home, End gibi tuşlara basıldığında yazıda bir değişiklik olmadığı halde loglarda arka arkaya aynı mesajlar gelmektedir. Şu durumda observable’a önceden yaptığımız gibi where ile filtre ekleyerek bunu yapabiliriz ancak tüm klavye kodlarına hakim olmamız gerekir. Şurada olduğu gibi;

var WikipediaSearch_keyup = Rx.Observable.fromEvent(WikipediaSearch, "keyup");

var SearchableTextEnteredEvent = WikipediaSearch_keyup

    .select(function (eventData) {

        return { text: eventData.srcElement.value, key: eventData.keyCode };

    }).where(function (val) {

        switch (val.key) {

            case 16 /* Shift */:

            case 17 /* Ctrl */:

            case 18 /* Alt */:

                ... /* ... ve daha nicesi: hata yapmam kaçınılmaz */

                return false;

            default:

                return true;

        }

    }).where(function (val) {

        return val.text.length >= 4;

    }).select(function (val) {

        return val.text;

    }).throttle(1000);

 

Bunun yerine Rx Js kütüphanesi işimizi kolaylaştıracak fonksyonlar sunmaktadır. DistinctUntilChanged fonksyonu tam da istediğimiz işi yapmaktadır;

var WikipediaSearch_keyup = Rx.Observable.fromEvent(WikipediaSearch, "keyup");

var SearchableTextEnteredEvent = WikipediaSearch_keyup

    .select(function (eventData) {

        return eventData.srcElement.value;

    }).where(function (text) {

        return text.length >= 4;

    }).throttle(1000)

    .distinctUntilChanged();

 

Throttle işleminin ardından elimize geçen veriyi distictUntilChanged işlemine tabi tutuyoruz diye de yorumlayabilirsiniz. Bu işlem sayesinde elimize geçen verinin 1 önce işlenen veriden farklı olması beklenmektedir ve ancak bu durumda event çalışmaktadır. DistinctUntilChanged eklemeden önce “asdf” yazılı iken Ctrl tuşuna bastığımızda loglarda iki defa “asdf”  yazdığını görecektik çünkü bir önceki ile aynı olup olmadığına dair bir kontrol yapmamıştık. Ancak şimdi DistinctUntilChanged fonksyonu ile her yeni gelen verinin bir önceki ile aynı olup olmadığı kontrol ediliyor ve aynı ise subscribe fonksyonunda verdiğimiz onNext fonksyonu çalışmıyor, diğer durumda çalışıyor.

4.     Rx Js – Ajax

En sık kullanılan asenkron işlemler arasında AJAX gelmektedir. Amacımız kullanıcının  girdiği yazıyı bir servise asenkron olarak gönderip bize ilgili kayıtların dönmesini sağlamak. Ardından bu kayıtları ekranda görüntüleyeceğiz. Özetlemek gerekirse uzaktaki bir veri kaynağına erişiceğiz ve geri dönüş değerlerini işleyip kullanıcının görmesini sağlayacağız. AJAX bildiğiniz üzere senkron ve asenkron olarak çalışabilmektedir: jQuery’den örnek verecek olursak “$.ajax({... ,async: false ...})” gibi bir kullanımda “async” parametresi AJAX’ın asenkron olup olmadığını belirtiyor. Bizim ihtiyacımız için asenkron kullanım uygun olmaktadır.

Reaktif programlama bildiğiniz gibi push-based yapılar için oldukça uygundur. Örneğin sosyal ağ sitelerindeki feed yapıları veya asenkron veri erişimleri gibi örnekler verilebilir. Bizim örneğimizde de asenkron veri erişimi olacaktır. Reaktif programlamanın en efektik kullanımı senkron ya da asenkron Request’e dönen Response’ın da asenkron olabildiği durumdur: çünkü ancak o zaman verinin bize gelmesi asenkron olmaktadır aksi takdirde Response tek seferde gelmektedir. Bunu yapmakta olduğumuz örnek ile açıklamaya çalışalım. Textbox içerisine “hello” yazdığımızı düşünelim ve “o” karakterine klavyede bastıktan 1 sn sonra Wikipedia’nın ilgili web servisine kelimeyi göndererek bir Request yapmış oluyoruz. Request asenkron olarak sunucuya iletilecek ve Response olarak beklentimiz bir kelimeler dizisi olacaktır. Eğer asenkron AJAX yapılmışsa elbetteki Response’da bize asenkron ulaşacaktır ve UI katmanımız bloke olmayacaktır. Ancak verinin bize ulaşması blok halinde olacaktır. Örneğin “hello” kelimesine ilişkin verilen cevapta 100.000 kelimelik bir dizi var ise tamamının dönmesini beklemek durumunda kullanıcı; aksi halde gelen kayıtları göremeyecektir. Geri dönen kayıt sayısının çok olması kullanıcı açısından dezavantaj olmaktadır; halbuki Wikipedi sunucuları Response içerisindeki kelimeleri teker teker gönderecek şekilde yapılandırılsalardı gerçek anlamda (sunucu için de) bir Reaktif programlama modeli uygulanmış olacaktı. Bu özelliğe “XHR Streaming” ya da “Comet like AJAX”  denilmektedir. Konuyla ilgili örneğe buradan ulaşabilirsiniz.

Ne yazık ki biz Wikipedia’nın web servislerini değil o servisleri kullanan web uygulamaları geliştiriyoruz. Dolayısıyla web servisleri tarafında değişiklik yapmamız client olarak söz konusu olamaz. Bu nedenle örneğimizde web servisin değil onu kullanan web uygulamasının Rx ile etkileşimini inceleyeceğiz.

Rx Js için verilen uyumluluk paketlerinin hemen hepsinde AJAX desteği bulunmaktadır. Şimdi bunu RxJs-HTML kullanarak gerçekleştirelim. Bu kütüphanede Rx.Observable için ajax, post, get, getJSON, getJSONPRequest fonksyonları tanımlanmıştır. Bu fonksyonların hepsi birer Observable nesne dönmektedir; yani bu fonksyonlar AJAX requesti yapmaktadır, ve gerekli Observer nesnesini döndürmektedir. Parametre olarak da “post” ve “ajax” fonksyonu haricinde URL bilgisi almaktadır. “post” fonksyonu URL ilebirlikte gönderilecek veriyi de almaktadır. “ajax” fonksyonu ise { url: “www.cronom.com”,  async:  true, method: “get” ... } gibi bir JSON türünden settings kullanımı ile AJAX Request için gerekli bilgiler verilmektedir. Bu fonksyonlar çalıştığı anda Request yapılmaktadır. Örnek kullanım aşağıdaki gibidir;

Rx.Observable.getJSON("http://www.cronom.com");

Rx.Observable.ajax({ url: "http://www.cronom.com/", method: "GET", async: false });

 

Wikipedia’da arama yapmak için JSONP kullanmamız gerekiyor. Normalde bir AJAX requesti herhangi bir adrese yapılamaz çünkü tarayıcı “Cross Domain” kontrolü yapmaktadır ve bu kontrole göre eğer AJAX ile erişmeye çalıştığımız URL base adresi ile tarayıcının görüntülemekte olduğu sayfanın URL base adresi aynı değil ise kullanıcıya bununla ilgili ikaz penceresi göstermektedir veya bazı tarayıcılar hata vermektedir.

IE

Chrome

Origin http://www.cronom.com is not allowed by Access-Control-Allow-Origin

 

Bu tarz erişimler elbetteki imkansız değildir: JSONP kullanılarak bu sorun aşılmaktadır. Bu konuda daha detaylı bilgi için şu linki incelemenizi tavsiye ederim. Biz de wikipedia’da arama yapmak için JSONP kullanacağız. Bununla ilgili olarak kodumuz aşağıdaki gibi olacaktır.

<script type="text/javascript" src="~/Scripts/RX/rx.js"></script>

<script type="text/javascript" src="~/Scripts/RX/rx.time.js"></script>

<script type="text/javascript" src="~/Scripts/RX/integration/rx.html.js"></script>

<script type="text/javascript">

    (function (window, undefined) {

 

        var window_load_subscription = Rx.Observable.fromEvent(window, "load").subscribe(

            function (loadEvent) {

                var WikipediaSearch_keyup = Rx.Observable.fromEvent(WikipediaSearch, "keyup");

 

                var SearchableTextEnteredEvent = WikipediaSearch_keyup

                    .select(function (eventData) {

                        return eventData.srcElement.value;

                    }).where(function (text) {

                        return text.length >= 4;

                    }).throttle(1000)

                    .distinctUntilChanged();

 

                var SearchResultReturnedEvent = SearchableTextEnteredEvent

                    .select(function (text) {

                        var cleanText = window.encodeURIComponent(text);

                        var url = 'http://en.wikipedia.org/w/api.php?action=opensearch&format=json&search=' + cleanText + '&callback=JSONPCallback';

                        return Rx.Observable.getJSONPRequest(url);

                    }).switchLatest()

                    .where(function (response) {

                        return response.length === 2;

                    }).select(function (response) {

                        return response[1];

                    });

 

                var SearchResultLogger = Rx.Observer.create(

                    function (suggestions) {

                        while (WikipediaSearch_Logs.hasChildNodes())

                            WikipediaSearch_Logs.removeChild(WikipediaSearch_Logs.lastChild);

 

                        for (var si = 0, sl = suggestions.length, li; si < sl; si++) {

                            WikipediaSearch_Logs.appendChild(document.createTextNode(suggestions[si]));

                            WikipediaSearch_Logs.appendChild(document.createElement("br"));

                        }

                    }, function (error) {

                        window.alert("İstenmeyen bir hata oluştu: " + error);

                    }, undefined

                );

                SearchResultReturnedEvent.subscribe(SearchResultLogger);

 

                window_load_subscription.dispose();

            },

            undefined,

            undefined

        );

 

    }(this));

</script>

 

Burada dikkat edilmesi gereken “SearchResultReturnedEvent” isimli nesnedir. Bu nesne üzerinde kullanılan ilk select fonksyon çağırımına dikkatinizi çekmek istiyorum;

SearchableTextEnteredEvent

.select(function (text) {

    var cleanText = window.encodeURIComponent(text);

    var url = 'http://en.wikipedia.org/w/api.php?action=opensearch&format=json&search=' + cleanText + '&callback=JSONPCallback';

    return Rx.Observable.getJSONPRequest(url);

}).switchLatest()

 

Burada JSONP çalıştırabilmek için URL’ye '&callback=JSONPCallback' gibi  bir partametre ekliyoruz. Arka planda Rx.Observable.getJSONPRequest fonksyonu kendisi kullanmaktadır. Son olarak switchLatest fonksyonu çağırılmıştır: bu fonksyon sayesinde select içerisinde dönülen Observable nesnesini döndürebiliyor. Rx kütüphanelerinde eğer bir Observable’daki işi başka bir Observable nesneye devredip devamını sağlamak istiyorsak genellikle bu kalıbı kullanırız: Önce select fonksyonu ile yeni bir Observable döndürülür ve ardından switchLatest fonksyonu ile bu Obseervable’a erişim sağlanır. Verdiğimiz örnekte SearchableTextEnteredEvent Observable nesnesinin işlerini Rx.Observable.getJSONPRequest(url) foksyonu ile dönen Observable nesnesine aktarıyoruz. Böylece switchLatest fonksyonundan sonra uygulanan tüm fonksyonlar sanki Rx.Observable.getJSONPRequest(url) foksyonu ile dönen Observable nesnesine uygulanıyormuş gibi düşünebilirsiniz.

5.     Rx Js – Sonuç

Rx Js ile Observer tasarım kalıbının tüm faydalarını kullanabiliyoruz. Buna ek olarak bize senkron ve asenkron işlemler için büyük kolaylık sağlıyor. Kesinlikle kullanmanızı öneririm. Rx kütüphanesinin en büyük faydasını HTML5 ile birlikte gelen Server-Sent Events (a.k.a. EventSource), WebSockets özelliklerinde göreceğiz. Ancak örneklerimizde de gördüğünüz üzere onlara da ihtiyaç duymadan istenileni yapabiliyoruz.

Verdiğim örneklerde ve kodlarda kullandığım yorum satırları ile, karşılaşabileceğiniz bazı hatalara ilişkin uyarılarımı bulabilirsiniz. Rx kütüphanesi Microsoft’un .Net platformunda doğrudan yerini almış durumda. Windows işletim sistemi ile çalışan akıllı telefonlarda bu kütüphaneleri kullanabileceğiz ancak Rx Js sayesinde geliştirmelerimizde .Net ile kısıtlı kalmayacağız; WinJS kullanımının oldukça yaygınlaşacağını düşnüyorum. Rx Js’in WinJS desteği de bulunmaktadır.

Rx Js ile ilgili örneklere ve uyumluluk kütüphanelerinin kullanımlarına ilişkin örneklere Github üzerinden erişebilirsiniz. Ayrıca daha kullanmadığımız Rx Js kütüphanelerine ilşkin örnekler de bulabilirsiniz. Ayrıca örnek ararken Rx Js için arama yapmanız şart değildir. Rx kütüphanesinin C# dilindeki örnekleri de size yol gösterebilir ; çünkü örneklerimizden gördüğünüz üzere kullanımları oldukça benzerdir.

Makalemi beğendiğinizi umarım. Görüşmek üzere.

Referanslar

(1)      http://en.wikipedia.org/wiki/Observer_pattern#Example

(2)      http://www.a-coding.com/2010/10/observer-pattern-in-objective-c.html

(3)      http://www.codeproject.com/Articles/328365/Understanding-and-Implementing-Observer-Pattern-in

(4)      https://github.com/Reactive-Extensions

(5)      http://rx.codeplex.com/

(6)      https://github.com/Reactive-Extensions/RxJS

(7)      https://rxjs.codeplex.com/

(8)      http://msdn.microsoft.com/en-us/data/gg577611

(9)      http://www.debugtheweb.com/test/teststreaming.aspx

(10)   http://en.wikipedia.org/wiki/JSONP

 

reactiveextensionsjavascriptcsharpwikijsonp
izzet sapkalioglu tarafından yazıldı.

Yorumlar

17 Şub, 2017 01:01

It really needs to teach people how to be carefull about health and you tought in a very good way.

Yorumlarınız

Yorum

Önizleme