JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Herkese selam. Sergey Omelnitsky temas halinde. Kısa bir süre önce reaktif programlamayla ilgili bir yayın düzenledim ve burada JavaScript'teki eşzamansızlıktan bahsettim. Bugün bu materyal üzerine notlar almak istiyorum.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Ancak ana materyale başlamadan önce bir giriş notu yazmamız gerekiyor. O halde tanımlarla başlayalım: Yığın ve kuyruk nedir?

Yığın öğeleri son giren ilk çıkar LIFO esasına göre elde edilen bir koleksiyondur

Kuyruk öğeleri ilk giren ilk çıkar FIFO esasına göre elde edilen bir koleksiyondur

Tamam, devam edelim.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

JavaScript tek iş parçacıklı bir programlama dilidir. Bu, yalnızca bir yürütme iş parçacığının ve işlevlerin yürütülmek üzere kuyruğa alındığı bir yığının olduğu anlamına gelir. Bu nedenle, JavaScript aynı anda yalnızca bir işlem gerçekleştirebilirken, diğer işlemler çağrılana kadar yığında sıralarını bekleyecektir.

Çağrı yığını Basitçe söylemek gerekirse, programda bulunduğumuz yer hakkındaki bilgileri kaydeden bir veri yapısıdır. Bir fonksiyona geçersek, girişini yığının en üstüne iteriz. Bir fonksiyondan döndüğümüzde yığının en üstündeki elemanı çıkarırız ve fonksiyonu çağırdığımız yere geri döneriz. Yığının yapabileceği tek şey budur. Ve şimdi son derece ilginç bir soru. Peki eşzamansızlık JavaScript'te nasıl çalışır?

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Aslında yığına ek olarak tarayıcıların WebAPI adı verilen şeyle çalışmak için özel bir kuyruğu vardır. Bu kuyruktaki işlevler ancak yığın tamamen temizlendikten sonra sırayla yürütülecektir. Ancak bundan sonra yürütme için kuyruktan yığına itilirler. Şu anda yığında en az bir öğe varsa yığına eklenemez. Tam olarak bu nedenle, işlevlerin zaman aşımına göre çağrılması genellikle zaman açısından kesin değildir, çünkü işlev doluyken kuyruktan yığına ulaşamaz.

Aşağıdaki örneğe bakalım ve adım adım uygulamaya başlayalım. Bakalım sistemde neler olacak.

console.log('Hi');
setTimeout(function cb1() {
    console.log('cb1');
}, 5000);
console.log('Bye');

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

1) Henüz hiçbir şey olmuyor. Tarayıcı konsolu temiz, çağrı yığını boş.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

2) Daha sonra çağrı yığınına console.log('Hi') komutu eklenir.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

3) Ve yerine getirildi

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

4) Daha sonra console.log('Hi') çağrı yığınından kaldırılır.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

5) Şimdi setTimeout(function cb1() {… }) komutuna geçin. Çağrı yığınına eklenir.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

6) setTimeout(function cb1() {… }) komutu yürütülür. Tarayıcı, Web API'sinin parçası olan bir zamanlayıcı oluşturur. Bir geri sayım gerçekleştirecektir.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

7) setTimeout(function cb1() {... }) komutu işini tamamladı ve çağrı yığınından kaldırıldı.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

8) console.log('Bye') komutu çağrı yığınına eklenir.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

9) console.log('Bye') komutu yürütülür.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

10) console.log('Bye') komutu çağrı yığınından kaldırıldı.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

11) En az 5000 ms geçtikten sonra zamanlayıcı sona erer ve geri arama cb1'i geri arama kuyruğuna yerleştirir.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

12) Olay döngüsü cb1 fonksiyonunu geri arama kuyruğundan alır ve onu arama yığınına yerleştirir.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

13) cb1 işlevi yürütülür ve console.log('cb1') dosyasını çağrı yığınına ekler.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

14) console.log('cb1') komutu yürütülür.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

15) console.log('cb1') komutu çağrı yığınından kaldırıldı.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

16) cb1 fonksiyonu çağrı yığınından kaldırıldı.

Dinamikteki bir örneğe bakalım:

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Eşzamansızlığın JavaScript'te nasıl uygulandığına baktık. Şimdi asenkron kodun evriminden kısaca bahsedelim.

Asenkron kodun evrimi.

a(function (resultsFromA) {
    b(resultsFromA, function (resultsFromB) {
        c(resultsFromB, function (resultsFromC) {
            d(resultsFromC, function (resultsFromD) {
                e(resultsFromD, function (resultsFromE) {
                    f(resultsFromE, function (resultsFromF) {
                        console.log(resultsFromF);
                    })
                })
            })
        })
    })
});

JavaScript'te bildiğimiz şekliyle eşzamansız programlama yalnızca işlevler tarafından uygulanabilir. Herhangi bir değişken gibi diğer işlevlere aktarılabilirler. Geri aramalar böyle doğdu. Ve üzüntüye, melankoliye ve üzüntüye dönüşene kadar havalı, eğlenceli ve şakacı. Neden? Basit:

  • Kodun karmaşıklığı arttıkça, proje hızla belirsiz, tekrar tekrar iç içe geçmiş bloklara, yani "geri arama cehennemine" dönüşür.
  • Hata işlemeyi gözden kaçırmak kolay olabilir.
  • İfadeleri return ile döndüremezsiniz.

Promise'ın gelişiyle durum biraz daha iyi hale geldi.

new Promise(function(resolve, reject) {
    setTimeout(() => resolve(1), 2000);

}).then((result) => {
    alert(result);
    return result + 2;

}).then((result) => {
    throw new Error('FAILED HERE');
    alert(result);
    return result + 2;

}).then((result) => {
    alert(result);
    return result + 2;

}).catch((e) => {
    console.log('error: ', e);
});

  • Kodun okunabilirliğini artıran söz zincirleri ortaya çıktı
  • Hataları yakalamak için ayrı bir yöntem ortaya çıktı
  • Promise.all kullanılarak paralel yürütme olanağı eklendi
  • İç içe geçmiş eşzamansızlığı async/await kullanarak çözebiliriz

Ancak vaatlerin de sınırları vardır. Mesela tefle dans edilmeden verilen söz iptal edilemez ve en önemlisi tek değerle çalışmasıdır.

Reaktif programlamaya sorunsuz bir şekilde yaklaştık. Yorgun? Neyse ki gidip biraz çay yapabilirsin, düşünebilirsin ve daha fazlasını okumak için geri gelebilirsin. Ve devam edeceğim.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

reaktif programlama veri akışlarına ve değişim yayılımına odaklanan bir programlama paradigmasıdır. Veri akışının ne olduğuna daha yakından bakalım.

// Получаем ссылку на элемент
const input = ducument.querySelector('input');

const eventsArray = [];

// Пушим каждое событие в массив eventsArray
input.addEventListener('keyup',
    event => eventsArray.push(event)
);

Bir giriş alanımız olduğunu düşünelim. Bir dizi oluşturuyoruz ve her giriş olayı keyup'ı için olayı dizimizde saklayacağız. Aynı zamanda dizimizin zamana göre sıralandığını da belirtmek isterim. Daha sonraki olayların indeksi daha önceki olayların indeksinden daha büyüktür. Böyle bir dizi, veri akışının basitleştirilmiş bir modelidir ancak henüz bir akış değildir. Bu dizinin güvenli bir şekilde akış olarak adlandırılabilmesi için, abonelere yeni verilerin geldiğini bir şekilde bildirebilmesi gerekir. Böylece akışın tanımına geldik.

Veri akışı

const { interval } = Rx;
const { take } = RxOperators;

interval(1000).pipe(
    take(4)
)

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

akım Verilerin değiştiğini gösterebilen, zamana göre sıralanmış bir veri dizisidir. Şimdi, bir eylemin, kodun farklı bölümlerinde birden fazla olayın çağrılmasını gerektirdiği bir kod yazmanın ne kadar kolay hale geldiğini hayal edin. Sadece akışa abone oluyoruz ve değişiklikler meydana geldiğinde bizi bilgilendirecek. Ve RxJs kütüphanesi bunu yapabilir.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

RxJS gözlemlenebilir diziler kullanan eşzamansız ve olay tabanlı programlarla çalışmaya yönelik bir kitaplıktır. Kütüphane temel bir tür sağlar izlenebilir, çeşitli yardımcı türler (Gözlemci, Zamanlayıcılar, Konular) ve olaylarla koleksiyonlarda olduğu gibi çalışmak için operatörler (haritala, filtrele, azalt, her ve JavaScript Array'deki benzerleri).

Bu kütüphanenin temel kavramlarını anlayalım.

Gözlemlenebilir, Gözlemci, Yapımcı

Gözlenebilir, inceleyeceğimiz ilk temel türdür. Bu sınıf RxJ uygulamasının ana bölümünü içerir. Abone olma yöntemi kullanılarak abone olunabilen gözlemlenebilir bir akışla ilişkilidir.

Observable, güncellemeler oluşturmak için sözde bir yardımcı mekanizma uygular. izlemek. Gözlemci için değerlerin kaynağına denir Yapımcı. Bu bir dizi, yineleyici, web soketi, bir tür olay vb. olabilir. Yani gözlemlenebilirin Yapımcı ile Gözlemci arasında bir iletken olduğunu söyleyebiliriz.

Observable üç tür Observer olayını yönetir:

  • sonraki – yeni veriler
  • hata - sıranın bir istisna nedeniyle sona ermesi durumunda oluşan hata. bu olay aynı zamanda dizinin tamamlanmasını da ima eder.
  • tamamlandı - dizinin tamamlandığıyla ilgili sinyal. Bu, artık yeni veri olmayacağı anlamına gelir.

Demoyu görelim:

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Başlangıçta 1, 2, 3 ve 1 saniye sonra değerlerini işleyeceğiz. 4'ü alıp akışımızı sonlandıracağız.

Sesli düşünmek

Sonra bunu anlatmanın, onun hakkında yazmaktan daha ilginç olduğunu fark ettim. 😀

abone

Bir akışa abone olduğumuzda yeni bir sınıf yaratırız abonebu bize yöntemi kullanarak abonelikten çıkma yeteneği verir çıkmak. Bu yöntemi kullanarak abonelikleri de gruplayabiliriz. eklemek. Eh, kullanarak iş parçacıklarının grubunu çözebilmemiz mantıklıdır. Kaldır. Ekleme ve kaldırma yöntemleri başka bir aboneliği girdi olarak kabul eder. Abonelikten çıktığımızda sanki abonelikten çıkma yöntemini çağırmış gibi tüm alt aboneliklerden de çıktığımızı belirtmek isterim. Devam etmek.

Akış türleri

SICAK
SOĞUK

Üretici gözlemlenebilirin dışında yaratılmıştır
Üretici gözlemlenebilirin içinde yaratılır

Veriler gözlemlenebilirin yaratıldığı anda aktarılır
Veriler abonelik sırasında sağlanır

Abonelikten çıkmak için ek mantığa ihtiyacınız var
Konu kendi kendine sona eriyor

Bire-çok ilişkisini kullanır
Bire bir ilişki kullanır

Tüm abonelikler aynı anlama sahiptir
Abonelikler bağımsızdır

Aboneliğiniz yoksa veriler kaybolabilir
Yeni bir abonelik için tüm akış değerlerini yeniden yayınlar

Bir benzetme yapmak gerekirse, sıcak akışı sinemadaki bir film olarak düşünürdüm. Hangi noktaya geldiniz, o andan itibaren izlemeye başladınız. Soğuk bir akışı teknolojideki bir çağrıya benzetirdim. Destek. Arayan kişi sesli mesaj kaydını baştan sona dinler, ancak aboneliği iptal etmeyi kullanarak telefonu kapatabilirsiniz.

Sıcak akışlar olarak da adlandırılan akışların da olduğunu belirtmek isterim (bu tanımla çok nadiren ve yalnızca yabancı topluluklarda karşılaştım) - bu, soğuk bir akıştan sıcak bir akışa dönüşen bir akıştır. Soru ortaya çıkıyor - nerede kullanılmalı)) Pratikten bir örnek vereceğim.

Angular'la çalışıyorum. Aktif olarak rxjs kullanıyor. Sunucuya veri almak için soğuk bir iş parçacığı bekliyorum ve bu iş parçacığını asyncPipe kullanarak şablonda kullanıyorum. Bu kanalı birkaç kez kullanırsam, soğuk akışın tanımına dönersek, her kanal sunucudan veri isteyecektir ki bu da en hafif deyimiyle tuhaftır. Ve eğer soğuk bir akışı sıcak bir akışa dönüştürürsem, o zaman istek bir kez gerçekleşecektir.

Genel olarak akış türlerini anlamak yeni başlayanlar için oldukça zordur ancak önemlidir.

Operatörler

return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
    .pipe(
        tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
        map(({ data }: TradeCompanyList) => data)
    );

Operatörler bize akışlarla çalışma yeteneğimizi genişletme olanağı sağlar. Gözlemlenebilirde meydana gelen olayların kontrol edilmesine yardımcı olurlar. En popüler olanlardan birkaçına bakacağız ve operatörler hakkında daha fazla ayrıntıyı faydalı bilgilerdeki bağlantıları kullanarak bulabilirsiniz.

Operatörler - arasında

Yardımcı operatörüyle başlayalım. Basit bir değere dayalı olarak bir Gözlenebilir oluşturur.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Operatörler - filtre

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Filtre operatörü, adından da anlaşılacağı gibi akış sinyalini filtreler. Operatör true değerini döndürürse daha da atlar.

Operatörler - al

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

take — İş parçacığının sona ermesinden sonra yayıcıların sayısının değerini alır.

Operatörler - debounceTime

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

debounceTime - çıkışlar arasında belirtilen zaman aralığına giren yayılan değerleri atar - zaman aralığı geçtikten sonra son değeri yayar.

const { Observable } = Rx;
const { debounceTime, take } = RxOperators;

Observable.create((observer) => {
  let i = 1;
  observer.next(i++);
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next(i++)
  }, 1000);

 // Испускаем значение раз в 1500мс
  setInterval(() => {
    observer.next(i++)
  }, 1500);
}).pipe(
  debounceTime(700),  // Ожидаем 700мс значения прежде чем обработать
  take(3)
);  

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Operatörler - takeWhile

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

TakeWhile false değerini döndürünceye kadar değerleri yayar, ardından konu aboneliğinden çıkar.

const { Observable } = Rx;
const { debounceTime, takeWhile } = RxOperators;

Observable.create((observer) => {
  let i = 1;
  observer.next(i++);
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next(i++)
  }, 1000);
}).pipe(
  takeWhile( producer =>  producer < 5 )
);  

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Operatörler - mergeLatest

CombineLatest operatörü, Promise.all'a biraz benzer. Birden fazla iş parçacığını tek bir iş parçacığında birleştirir. Her iş parçacığı en az bir emisyon yaptıktan sonra her birinden en son değerleri dizi şeklinde alıyoruz. Ayrıca birleşen derelerden yapılacak herhangi bir emisyon sonrasında yeni değerler verecektir.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

const { combineLatest, Observable } = Rx;
const { take } = RxOperators;

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
});

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 750мс
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
});

combineLatest(observer_1, observer_2).pipe(take(5));

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Operatörler - zip

Zip - Her iş parçacığından bir değer bekler ve bu değerlere göre bir dizi oluşturur. Değer herhangi bir iş parçacığından gelmiyorsa grup oluşturulmayacaktır.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

const { zip, Observable } = Rx;
const { take } = RxOperators;

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
});

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 750
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
});

const observer_3 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 500
  setInterval(() => {
    observer.next('c: ' + i++);
  }, 500);
});

zip(observer_1, observer_2, observer_3).pipe(take(5));

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Operatörler - forkJoin

forkJoin ayrıca konuları birleştirir, ancak yalnızca tüm iş parçacıkları tamamlandığında bir değer yayar.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

const { forkJoin, Observable } = Rx;
const { take } = RxOperators;

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
}).pipe(take(3));

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 750
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
}).pipe(take(5));

const observer_3 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 500
  setInterval(() => {
    observer.next('c: ' + i++);
  }, 500);
}).pipe(take(4));

forkJoin(observer_1, observer_2, observer_3);

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Operatörler - harita

Harita dönüştürme operatörü, yayıcı değerini yeni bir değere dönüştürür.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

const {  Observable } = Rx;
const { take, map } = RxOperators;

Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next(i++);
  }, 1000);
}).pipe(
  map(x => x * 10),
  take(3)
);

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Operatörler – paylaşın, dokunun

Dokunma operatörü, yan etkileri, yani sırayı etkilemeyen eylemleri gerçekleştirmenize olanak tanır.

Paylaşım hizmet operatörü soğuk bir akışı sıcak bir akışa dönüştürebilir.

JavaScript'te eşzamansız programlama (Geri Arama, Söz, RxJ'ler)

Operatörlerle işimiz bitti. Konuya Geçelim.

Sesli düşünmek

Daha sonra çay içmeye gittim. Bu örneklerden bıktım 😀

Konu ailesi

Söz konusu aile, sıcak akışların başlıca örneğidir. Bu sınıflar aynı anda gözlemlenebilir ve gözlemci olarak hareket eden bir tür melezdir. Konu sıcak bir konu olduğundan aboneliğinizi iptal etmeniz gerekmektedir. Ana yöntemlerden bahsedersek, bunlar:

  • sonraki – yeni verilerin akışa aktarılması
  • hata – hata ve iş parçacığının sonlandırılması
  • tamamlandı – iş parçacığının tamamlanması
  • abone olun – bir akışa abone olun
  • aboneliği iptal et – akış aboneliğinden çık
  • asObservable – bir gözlemciye dönüşür
  • toPromise – bir söze dönüşür

4 5 konu türü vardır.

Sesli düşünmek

Yayında 4 kişi konuşuyordu ama bir tane daha ekledikleri ortaya çıktı. Dedikleri gibi yaşa ve öğren.

Basit Konu new Subject()– en basit konu türü. Parametreler olmadan oluşturulmuştur. Yalnızca abonelikten sonra alınan değerleri iletir.

DavranışKonu new BehaviorSubject( defaultData<T> ) – bence en yaygın konu türü. Giriş varsayılan değeri alır. Abone olurken iletilen son sayının verilerini her zaman kaydeder. Bu sınıf aynı zamanda akışın geçerli değerini döndüren kullanışlı bir değer yöntemine de sahiptir.

Tekrar Konusu new ReplaySubject(bufferSize?: number, windowTime?: number) — Giriş, isteğe bağlı olarak, kendi içinde depolayacağı değerler arabelleğinin boyutunu ilk argüman olarak, ikinci olarak ise değişiklik yapmamız gereken zamanı alabilir.

AsyncSubject new AsyncSubject() — abone olurken hiçbir şey olmuyor ve değer yalnızca tamamlandığında döndürülecek. Akışın yalnızca son değeri döndürülecektir.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) — Belgeler onun hakkında sessiz ve onu ilk kez görüyorum. Ne yaptığını bilen varsa yazsın biz de ekleyelim.

Vay be. Bugün sana söylemek istediğim her şeyi anlattık. Umarım bu bilgi faydalı olmuştur. Yararlı bilgiler sekmesinde referans listesini kendiniz okuyabilirsiniz.

Yararlı bilgiler

Kaynak: habr.com

Yorum ekle