Rxjs Observables ve Angular

Nahit Ferhat Ektaş
7 min readOct 13, 2023

--

Bilindiği gibi Javascript single-threaded bir dildir. Yani yazdığımız kodlar satır satır çalışır. Her yeni satır kod çalıştırılmak için, bir önceki satırın çalıştırılmasını bekler. Normal şartlarda bu durum geliştirici için gayet normal bir durumdur. Ancak http server’lara yaptığımız istekler olduğunda, sunucudan cevap dönmesi biraz zaman alabilir. Örneğin bir rest api’ ye istek attığımızda sonucun bize dönmesi biraz zaman alabilir, bu sürenin 3 saniye olduğunu düşünelim. Rest api’ye istek attığımız kod satırı main thread’imizi 3 saniye bloklayacak, 3 saniye sonunda işlem bittiği zaman bir alt satırdaki kodumuz çalışmaya başlayacak. http server’lara yaptığımız isteğin sonucunu beklediğimiz süre içerisinde, diğer kodların main thread’imizi bloklamayarak çalışmasına asenkron programlama denir.

Asenkron programlamayı javascript’in bize sağlamış olduğu Promise’ler ile gerçekleştirebiliyoruz. Bir diğer yöntem ise, yazının başlığını oluşturan observable kullanarak asenkron programla gerçekleştirebiliyoruz. Observable javascript kütüphanesi olan Rxjs de yer alır. Observable kullanmak için, Rxjs kütüphanesini projemize import etmemiz yeterli olacaktır.

Observable Nedir?

Observable, verilerimizi saklayan ve sonrasında kendine abone olan observer’lara ileten bir veri kaynağı olarak da değerlendirilebilir. Rxjs kütüphanesini uygulamamıza ekleyerek kullanabiliriz. Bir observable nesnesi oluşturduğumuzda, artık bu nesne bizim için gözlemlenebilir şekilde oluşmuştur ve bu nesnenin içerisine event yada veriler ekleyerek değişimlerini takip etmemize olanak tanır.

Observable da yer alan verilere, observer’ler ile subscribe olarak tüketebiliriz.

Observer ve subscribe nedir? Daha yakından inceleyelim.

Observer: Observable içerisinde yer alan veriyi gözlemler ve tüketmemizi sağlar. 3 tane property alır. Bunlar;

1- Next: Veriyi kullanmamızı sağlar.

2- Error: Hata olması durumunda hatayı döndürür.

3- Complete: Hatasız bir şekilde işlem tamamlanmış ise çalışır.

Subscribe: Observable’dan veri tüketilme işleminin başlatılmasıdır. Observable nesneler, observer’lar subscribe olduktan sonra veri dağıtmaya başlar. Eğer subscribe olmazsak herhangi bir değer yaymaz. İşlem complete olduğunda subscription işlemi sonlanır.

export class AppComponent implements OnInit {
myObservable$ = new Observable<number>((observer) => {
observer.next(1);
observer.next(4);
observer.next(6);
observer.next(7);
observer.next(8);
});

ngOnInit(): void {
this.myObservable$.subscribe((val) => console.log(val));
}
}

Yukarıdaki örnekte, bir observable nesnesi oluşturduk, ve içerisine next ile değerler ekledik. Component ilk yüklendiğinde subscribe olarak observable nesnemizi tüketmeye başladık.

Promises ve Observables Farkları Nelerdir?

Observable için, promise’lerin daha güçlü bir versiyonu olduğunu söyleyebiliriz. İkisi de asenkron işlemlerin sonucunu döndürürler.

Promise’ler eager loading ile yüklenirken, observable’ler lazy loading ile yüklenir.

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

@Component({
selector: 'app-root',
template: `<ng-container>
<h1>Observable in ANGULAR</h1>
</ng-container>`,
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
myObservable$!: Observable<string>;
myPromise!: Promise<string>;

ngOnInit(): void {
//Observable
this.myObservable$ = new Observable<string>((observer) => {
console.log('Observable has created');
observer.next('Observable has emitted 1');
observer.next('Observable has emitted 2');
observer.next('Observable has emitted 3');
});

//Promise
this.myPromise = new Promise<string>((resolve) => {
console.log('Promise has created');
resolve('Promise has emitted 1');
resolve('Promise has emitted 2');
resolve('Promise has emitted 3');
});
}
}

Yukarıdaki örnekte component init olduğunda bir tane promise bir tanede observable nesne yarattık. Promise javascript de yer aldığı için herhangi bir import’a gerek duymadık ama observables rxjs kütüphanesi içerisinde geldiği için rxjs’i import ettik. Component init olduğunda console’ a emit edilen değerleri yazdırmak istiyorum. Şimdi çıktıya bakalım.

OnInit de hem observable hem de promise oluşturmama rağmen yalnızca promise ekranda çıktı olarak gözüktü, bunun nedeni yukarıda da belirttiğim gibi, promises eager loading olarak yüklenir. Aslında observable nesnemde bellek de yaratıldı ancak lazy loading olduğu için yükleme işlemi gerçekleşmedi. Bunu bir http request olarak düşünürsek, promise oluşturulduğu anda api’ye istek atmış oldu, sonrasında then diyerek request’in sonucunu alabilirim.

Observable’ler ise, subscribe olunduğunda tetiklenir.

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

@Component({
selector: 'app-root',
template: `<ng-container>
<h1>Observable in ANGULAR</h1>
<button (click)="getData()">Action</button>
</ng-container>`,
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
myObservable$!: Observable<string>;
myPromise!: Promise<string>;

ngOnInit(): void {
//Observable
this.myObservable$ = new Observable<string>((observer) => {
console.log('Observable has created');
observer.next('Observable has emitted 1');
observer.next('Observable has emitted 2');
observer.next('Observable has emitted 3');
});

//Promise
this.myPromise = new Promise<string>((resolve) => {
console.log('Promise has created');
resolve('Promise has emitted 1');
resolve('Promise has emitted 2');
resolve('Promise has emitted 3');
});
}

getData() {
this.myPromise.then((res) => console.log(res));
this.myObservable$.subscribe((res) => console.log(res));
}
}

Aynı örneğimize getData() adında bir fonksiyon ekledim ve bu fonksiyon Action button’u click olduğunda tetikleniyor. Çıktıya hep beraber bakalım.

getData() fonksiyonu çağırıldığında oluşan ekran görüntüsü

“Promise has created” yazısı promise ilk oluştuğunda zaten yazmıştı. Button click olduğunda, subscription işlemimizi gerçekleştirdik ve lazy loading ile çalışan observable nesnemiz tetiklenmiş oldu. Daha sonrasında zaten ilk oluşturduğumuzda çalışan promise’ den beklediğimiz çıktıyı almış olduk.

Burada önemli olan bir başka konuda, biz hem observable nesnemizde hem de promise nesnemizde “Observable has emitted 1–2–3” ve “Promise has emitted 1–2–3” şeklinde 3'er tane kayıt emit ettik. Ancak yukarıdaki örneğe baktığımızda yalnızca observable olanların tamamının çıktısını görebiliyoruz. Promise den ise yalnızca ilk değer geliyor. Bunun sebebi, observable ile promise arasındaki bir diğer farktan kaynaklanıyor.

Promise’ler yalnızca tek bir değer return eder, observable’ ler ise, bir stream(akış) içerisinde, akışa abonelik bitene kadar veya akış tamamlanana kadar birden fazla değer return edebilir.

Promise çalıştığında yalnızca bir değer döner, buna karşın observable birden fazla değer dönebilirler.

Promise her zaman asenkron çalışırken, observable hem senkron hem de asenkron olarak çalışabilir.

Promise: Her zaman asenkron çalışır. Return olan değer senkron olsa bile promise bize asenkron olarak dönüş yapacaktır.

Observable: Hem asenkron hem de senkron olarak çalışabilir. Eğer emit ettiğimiz değer senkron ise senkron bir davranış, asenkron ise asenkron bir davranış sergiler.

export class AppComponent implements OnInit {
myPromise!: Promise<string>;

ngOnInit(): void {
this.myPromise = new Promise((resolve, reject) => {
resolve('success');
});
this.myPromise.then((value) => {
console.log(value);
});
console.log('Promise complete');
}

}

Yukarıdaki örnekte, bir tane promise nesnesi oluşturduk ve resolve(‘success’) mesajını döndürdük.

Promise Örneği Çıktısı

Beklememiz gereken bir işlem olmamasına rağmen success mesajı, promise ile asenkron olarak çalıştı ve “Promise complete” mesajından önce yazılmasına rağmen console’de ikinci sırada yazdı. Yani aslında promise yapısı bize işlemi asenkron olarak döndü.

export class AppComponent implements OnInit {
myObservable$: Observable<string> = new Observable((subscriber) => {
subscriber.next('item-1');
subscriber.next('item-2');
subscriber.next('item-3');
});
ngOnInit(): void {
this.myObservable$.subscribe((value) => {
console.log(value);
});
console.log('Observable Complete');
}
}

Yukarıdaki örnekte, observable nesne oluşturduk(myObservable$) ve içerisine 3 tane eleman ekledik. ngOnInit ile de abone olduk.

Observable Senkron Örnek Çıktısı

Subscribe olduğumuz myObservable$ bize senkron olarak elemanları döndürdü, çıktılarımız beklediğimiz gibi sırası ile ekrana yazılmış oldu.

export class AppComponent implements OnInit {
myObservable$: Observable<string> = new Observable((subscriber) => {
subscriber.next('item-1');
subscriber.next('item-2');
subscriber.next('item-3');
setTimeout(() => {
//emitting value Asynchronously
subscriber.next('item-4');
}, 2000);
});
ngOnInit(): void {
this.myObservable$.subscribe((value) => {
console.log(value);
});
console.log('Observable Complete');
}
}

Aynı kod örneğine, setTimeout ile 2 saniye gecikmeli olarak “item-4” elemanını ekledik. Bunu servise istek yaptık ve response 2 saniye sonra geldi gibide değerlendirebiliriz.

Observable Asenkron Örnek Çıktısı

İşlemi 2 saniye beklettiğimiz için, abone olduğumuz observable nesnemiz asenkron bir davranış sergilemiş oldu.

Promise de yapılan istekler iptal edilemez, ancak observable istekler iptal edilebilir(Cancellable and Non-Cancellable)

import { Component, OnInit } from '@angular/core';
import { Observable, Subscription } from 'rxjs';

@Component({
selector: 'app-root',
template: `<ng-container>
<h1>Observable in ANGULAR</h1>
<button (click)="cancel()">Action</button>
</ng-container>`,
})
export class AppComponent implements OnInit {
subscription$!: Subscription;
myObservable$: Observable<number> = new Observable((subscriber) => {
let counter = 0;

setInterval(() => {
subscriber.next(counter++);
}, 1000);
});
ngOnInit(): void {
this.subscription$ = this.myObservable$.subscribe((value) => {
console.log(value);
});
}

cancel() {
this.subscription$.unsubscribe();
console.log('Subscription is cancelled!!');
}
}

Yukarıdaki örnekte, observable nesnesine(myObservable$) counter adında bir değişken oluşturduk. Değişken setInterval() ile her saniye geçtiğinde +1 oluyor. Böylece bir akış elde etmiş oluyoruz. Component ilk yüklendiğinde bu akışa abone oluyoruz.

Buraya kadar olan bölümde, abone olduğum observable nesnesinden her saniye değer gelmesini ve bu değerin ekranda gösterilmesini bekliyorum.

Eğer promise kullanmış olsaydım böyle bir akış olmayacaktı ve gelen ilk değer dışındaki hiç bir değer ekran yazılmamakla beraber yapmış olduğum isteği de iptal etmem söz konusu olamayacaktı.

Ancak observable kullandığımızda isteklerimizi akış tamamlanmasa bile iptal edebiliyoruz. Component’in içerisine subscription$ adında ve Subscription tipinde bir değişken daha tanımladım. abone olduğum observable nesnesini bu değişkene atadım. Artık observable nesneme subscription$ üzerinden erişebileceğim. Action butonunun click event’inde çağırdığım cancel() fonksiyonu ile observable nesnemi atadığım subscription nesnesinin unsubscribe() methodunu çağırdığımda artık aboneliğim iptal olacaktır.

Promise’ler yalnızca bir noktadan tüketilebilir, observables ise birden fazla aboneye veri sağlayabilirler.

Promise kullandığımızda talep ettiğimiz veriye tek bir yerden erişim sağlayabiliriz. Promise nesnemizi then() ile birden fazla kez çağırsak bile sonuç aynı olacaktır.

export class AppComponent implements OnInit {
ngOnInit(): void {
const myPromise = new Promise<number>((resolve, _reject) => {
console.log('promise is created');
resolve(1);
});

myPromise.then((value) => {
console.log('first - ', value);
});

myPromise.then((value) => {
console.log('second - ', value);
});
}
}

Ancak observable kullandığımızda, observable nesnemiz kendisine abone olan observer’ın her birine ayrı ayrı yayın yapabilir.

Observable ve Promise arasında başka bir çok farklılık bulunuyor. Önemli olduğunu düşündüklerimi elimden geldiği kadar anlatmaya çalıştım. Rxjs, Angular’a özgü olmasa da, Angular mimarisinde oldukça yoğun bir şekilde kullanılıyor ve temelinde observable yapısı var. Bu yazıda Observable nedir? Neden kullanmalıyız? Promise’den farkı nedir? gibi konulardan bahsetmeye çalıştım. Umarım faydalı olmuştur. Anlaşılamayan kısımlar için nahitektas@gmail.com üzerinden ulaşabilirsiniz.

--

--