Hamıya salam. Əlaqə Omelnitsky Sergey. Bir müddət əvvəl mən JavaScript-də asinxroniya haqqında danışdığım reaktiv proqramlaşdırma üzrə bir axın təşkil etdim. Bu gün mən bu materialı ümumiləşdirmək istərdim.
Ancaq əsas materiala başlamazdan əvvəl bir giriş etmək lazımdır. Beləliklə, təriflərdən başlayaq: yığın və növbə nədir?
Yığın elementləri "son girən, ilk çıxan" LIFO əsasında əldə edilən kolleksiyadır
Növbə elementləri (“ilk girən, birinci çıxan” FİFO) prinsipinə uyğun alınan kolleksiyadır
Yaxşı, davam edək.
JavaScript tək yivli proqramlaşdırma dilidir. Bu o deməkdir ki, onun yalnız bir icra başlığı və funksiyaların icra üçün növbəyə qoyulduğu bir yığın var. Buna görə də, JavaScript eyni anda yalnız bir əməliyyatı yerinə yetirə bilər, digər əməliyyatlar isə çağırılana qədər stekdə öz növbəsini gözləyəcək.
Zəng yığın sadə dillə desək, proqramda olduğumuz yer haqqında məlumatları qeyd edən verilənlər strukturudur. Funksiyaya tullansaq, onun girişini yığının yuxarı hissəsinə itələyirik. Funksiyadan qayıtdıqda, biz yığından ən yuxarı elementi çıxarırıq və bu funksiyanı çağırdığımız yerdə bitiririk. Bu yığının edə biləcəyi hər şeydir. Və indi çox maraqlı bir sual. JavasScript-də asinxroniya necə işləyir?
Əslində, yığına əlavə olaraq, brauzerlərdə sözdə WebAPI ilə işləmək üçün xüsusi növbə var. Bu növbənin funksiyaları yalnız yığın tamamilə təmizləndikdən sonra ardıcıllıqla yerinə yetiriləcək. Yalnız bundan sonra onlar icra üçün növbədən yığına yerləşdirilir. Əgər hazırda yığında ən azı bir element varsa, o zaman onlar yığına daxil ola bilməzlər. Məhz buna görə funksiyaların zaman aşımı ilə çağırılması çox vaxt düzgün olmur, çünki funksiya dolu olduqda növbədən yığına keçə bilmir.
Gəlin aşağıdakı nümunəyə nəzər salaq və onu addım-addım nəzərdən keçirək. Gəlin sistemdə nə baş verdiyini də görək.
1) Hələlik heç nə baş vermir. Brauzer konsolu təmizdir, zəng yığını boşdur.
2) Sonra console.log('Hi') əmri zəng yığınına əlavə edilir.
3) Və yerinə yetirildi
4) Sonra console.log('Hi') zəng yığınından silinir.
5) İndi setTimeout(funksiya cb1() {… }) əmrinə keçək. O, zəng yığınına əlavə olunur.
6) setTimeout(funksiya cb1() {… }) əmri yerinə yetirilir. Brauzer Web API-nin bir hissəsi olan taymer yaradır. O, geri sayma həyata keçirəcək.
7) setTimeout(funksiya cb1() {… }) əmri öz işini tamamladı və zəng yığınından silindi.
8) console.log('Bye') əmri zəng yığınına əlavə edilir.
9) console.log('Bye') əmri yerinə yetirilir.
10) console.log('Bye') əmri zəng yığınından silindi.
11) Ən azı 5000ms keçdikdən sonra taymer bitir və cb1 geri çağırışını geri çağırış növbəsinə qoyur.
12) Hadisə dövrəsi cb1 funksiyasını geri çağırış növbəsindən götürür və onu zəng yığınına itələyir.
13) cb1 funksiyası yerinə yetirilir və çağırış yığınına console.log('cb1') əlavə edir.
14) console.log('cb1') əmri yerinə yetirilir.
15) console.log('cb1') əmri zəng yığınından silinir.
16) cb1 funksiyası zəng yığınından çıxarılır.
Dinamikada bir nümunəyə baxaq:
Yaxşı, biz JavaScript-də asinxroniyanın necə həyata keçirildiyinə baxdıq. İndi asinxron kodun təkamülü haqqında qısaca danışaq.
Asinxron kodun təkamülü.
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-də bildiyimiz kimi asinxron proqramlaşdırma yalnız funksiyalarla edilə bilər. Onlar hər hansı digər dəyişən kimi digər funksiyalara ötürülə bilər. Geri çağırışlar belə yarandı. Kədər, melankoliya və kədərə çevrilənə qədər sərin, əyləncəli və qızğındır. Niyə? Bəli, sadədir:
Kodun mürəkkəbliyi artdıqca, layihə tez bir zamanda qaranlıq çoxlu iç-içə bloklara - “geri çağırış cəhənnəmi”nə çevrilir.
Səhvlərin idarə edilməsi asanlıqla nəzərdən qaçırıla bilər.
İfadələri return ilə qaytara bilməzsiniz.
Promise-in gəlməsi ilə vəziyyət bir az yaxşılaşdı.
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 oxunuşunu yaxşılaşdıran vəd zəncirləri meydana çıxdı
Səhvlərin qarşısını almaq üçün ayrıca bir üsul var idi
Promise.all ilə paralel icra əlavə edildi
İç içə asinxroniyanı async/await ilə həll edə bilərik
Lakin vədin öz məhdudiyyətləri var. Məsələn, bir vəd, qafla rəqs etmədən, ləğv edilə bilməz və ən əsası, bir dəyərlə işləyir.
Yaxşı, burada reaktiv proqramlaşdırmaya rəvan yaxınlaşırıq. Yorğun? Yaxşısı odur ki, bir neçə qağayı dəmləməyə, beyin fırtınasına gedə və daha çox oxumaq üçün qayıda bilərsiniz. Və davam edəcəyəm.
Reaktiv proqramlaşdırma - məlumat axınına və dəyişikliklərin yayılmasına yönəlmiş proqramlaşdırma paradiqması. Məlumat axınının nə olduğuna daha yaxından nəzər salaq.
// Получаем ссылку на элемент
const input = ducument.querySelector('input');
const eventsArray = [];
// Пушим каждое событие в массив eventsArray
input.addEventListener('keyup',
event => eventsArray.push(event)
);
Təsəvvür edək ki, bizdə bir giriş sahəsi var. Biz massiv yaradırıq və giriş hadisəsinin hər bir düyməsi üçün hadisəni massivimizdə saxlayacağıq. Eyni zamanda qeyd etmək istərdim ki, massivimiz zamana görə sıralanır, yəni. sonrakı hadisələrin indeksi əvvəlki hadisələrin indeksindən böyükdür. Belə bir massiv sadələşdirilmiş məlumat axını modelidir, lakin hələ axın deyil. Bu massivin etibarlı şəkildə axın adlandırılması üçün o, abunəçilərə yeni məlumatların daxil olması barədə bir şəkildə məlumat verə bilməlidir. Beləliklə, axının tərifinə gəlirik.
Akış verilənlərin dəyişdiyini göstərə bilən zamana görə çeşidlənmiş verilənlər massividir. İndi təsəvvür edin ki, bir hərəkət üçün kodun müxtəlif hissələrində bir neçə hadisəni işə salmaq lazım olan kodu yazmaq nə qədər rahatdır. Biz sadəcə olaraq yayıma abunə oluruq və o, dəyişikliklərin nə vaxt baş verdiyini bizə xəbər verəcəkdir. Və RxJs kitabxanası bunu edə bilər.
RxJS müşahidə olunan ardıcıllıqlardan istifadə edərək asinxron və hadisəyə əsaslanan proqramlarla işləmək üçün kitabxanadır. Kitabxana əsas növü təqdim edir Müşahidə olunur, bir neçə köməkçi növü (Müşahidəçilər, Planlaşdırıcılar, Subyektlər) və kolleksiyalarda olduğu kimi hadisələrlə işləmək üçün operatorlar (xəritə, filtr, azaltmaq, hər və JavaScript Array-dan oxşarlar).
Bu kitabxananın əsas anlayışlarını anlayaq.
Müşahidə olunan, müşahidəçi, prodüser
Müşahidə olunan, baxacağımız ilk əsas növdür. Bu sinif RxJs tətbiqinin əsas hissəsini ehtiva edir. O, abunə metodundan istifadə etməklə abunə oluna bilən müşahidə olunan axınla əlaqələndirilir.
Observable, sözdə yeniləmələrin yaradılması üçün köməkçi mexanizm tətbiq edir Müşahidəçi. Müşahidəçi üçün dəyərlərin mənbəyi deyilir Istehsalçı. Bu massiv, iterator, veb yuvası, bir növ hadisə və s. ola bilər. Beləliklə deyə bilərik ki, müşahidə olunan İstehsalçı və Müşahidəçi arasında bir dirijordur.
Müşahidə edilə bilən üç növ Müşahidəçi hadisəsini idarə edir:
növbəti - yeni məlumatlar
xəta - ardıcıllığın bir istisnaya görə dayandırıldığı səhvdir. bu hadisə həm də ardıcıllığın sonunu nəzərdə tutur.
tamamlandı - ardıcıllığın sonu haqqında bir siqnal. Bu o deməkdir ki, daha yeni məlumat olmayacaq
Bir demoya baxaq:
Başlanğıcda 1, 2, 3 və 1 saniyədən sonra dəyərləri emal edəcəyik. 4 alırıq və mövzumuzu bitiririk.
Yüksək səslə düşünmək
Və sonra anladım ki, bu haqda yazmaqdansa, danışmaq daha maraqlıdır. 😀
abunə
Axına abunə olanda yeni sinif yaradırıq abunə, bu bizə metodla abunəlikdən çıxmaq imkanı verir Unsubscribe. Metoddan istifadə edərək abunələri də qruplaşdıra bilərik əlavə etmək. Yaxşı, məntiqlidir ki, istifadə edərək mövzuları ayıra bilərik aradan qaldırılması. Əlavə etmə və silmə üsulları fərqli abunəliyi giriş kimi qəbul edir. Qeyd etmək istərdim ki, biz abunəni ləğv edəndə bütün uşaq abunəliklərindən imtina edirik, sanki onlar abunədən çıxmaq metodunu da çağırıblar. Davam et.
Axın növləri
HOT
TƏCİLİ
İstehsalçı müşahidə edilə biləndən kənarda yaradılmışdır
İstehsalçı müşahidə edilə bilən daxilində yaradılmışdır
Məlumat müşahidə edilə bilənin yaradıldığı anda ötürülür
Məlumat abunə zamanı verilir.
Abunəni ləğv etmək üçün daha çox məntiq lazımdır
Mövzu öz-özünə bitir
Bir-çox əlaqəsindən istifadə edir
Bir-bir münasibətdən istifadə edir
Bütün abunələr eyni dəyərə malikdir
Abunəliklər müstəqildir
Abunə olmadıqda məlumatlar itirilə bilər
Yeni abunə üçün bütün axın dəyərlərini yenidən nəşr edir
Bənzətmə üçün kinoteatrdakı film kimi qaynar bir axını təsəvvür edərdim. Hansı vaxtda gəldin, o andan baxmağa başladın. Soyuq bir axını bundakı zənglə müqayisə edərdim. dəstək. İstənilən zəng edən şəxs cavab verən maşının qeydini əvvəldən axıra kimi dinləyir, lakin siz abunəni ləğv etməklə telefonu bağlaya bilərsiniz.
Qeyd etmək istərdim ki, isti axınlar da var (mən belə bir tərifə çox nadir hallarda rast gəldim və yalnız xarici icmalarda rast gəldim) - bu, soyuq bir axından istiyə çevrilən bir axındır. Sual yaranır - harada istifadə etmək olar)) Təcrübədən bir nümunə verəcəyəm.
Mən Angular ilə işləyirəm. O, rxjs-dən aktiv şəkildə istifadə edir. Serverə məlumat əldə etmək üçün mən soyuq axın gözləyirəm və mən asyncPipe istifadə edərək şablonda bu axını istifadə edirəm. Bu borudan bir neçə dəfə istifadə etsəm, soyuq axının tərifinə qayıdaraq, hər bir boru serverdən məlumat tələb edəcək, ən azı qəribədir. Soyuq axını istiyə çevirsəm, sorğu bir dəfə baş verəcəkdir.
Ümumiyyətlə, axınların növünü başa düşmək yeni başlayanlar üçün olduqca çətindir, lakin vacibdir.
Operatorlar
return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
.pipe(
tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
map(({ data }: TradeCompanyList) => data)
);
Operatorlar bizə axınlarla işləmək imkanı verir. Onlar Müşahidə olunanda baş verən hadisələri idarə etməyə kömək edir. Ən populyarlarından bir neçəsini nəzərdən keçirəcəyik və operatorlar haqqında daha çox məlumatı faydalı məlumatdakı keçidlərdə tapa bilərsiniz.
operatorları
-nin köməkçi operatoru ilə başlayaq. Sadə bir dəyər əsasında Müşahidə Olunan yaradır.
Operatorlar-filtr
Filtr operatoru, adından da göründüyü kimi, axın siqnalını süzür. Operator true qaytarırsa, daha da atlayır.
Operatorlar - götürün
almaq - Emissiyaların sayının dəyərini alır, bundan sonra axın başa çatır.
Operatorlar-debounceTime
debounceTime - çıxış məlumatları arasında müəyyən vaxt intervalına düşən emissiya edilmiş dəyərləri ləğv edir - vaxt intervalı keçdikdən sonra sonuncu dəyəri verir.
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)
);
Operatorlar-takeWhile
TakeWhile false qaytarana qədər dəyərlər yayır, sonra axın abunəliyini ləğv edir.
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 )
);
Operatorlar-combineLatest
combineLatest birləşdirilmiş operatoru bir qədər dictionary.all ilə oxşardır. Birdən çox axını birləşdirir. Hər bir ip ən azı bir emissiya etdikdən sonra hər birindən massiv kimi ən son dəyərləri alırıq. Bundan əlavə, birləşdirilmiş axınlardan hər hansı bir emissiyadan sonra yeni dəyərlər verəcəkdir.
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));
Operatorlar-zip
Zip - hər axından dəyər gözləyir və bu dəyərlər əsasında massiv əmələ gətirir. Əgər dəyər hər hansı bir mövzudan gəlmirsə, o zaman qrup yaranmayacaq.
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));
Operatorlar - forkJoin
forkJoin də mövzulara qoşulur, lakin o, yalnız bütün mövzular tamamlandıqda bir dəyər verir.
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);
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)
);
Operatorlar - paylaşın, vurun
Tap operatoru yan təsirləri, yəni ardıcıllığa təsir etməyən hər hansı hərəkətləri etməyə imkan verir.
Paylaşım kommunal operatoru soyuq axını isti axına çevirə bilər.
Operatorlar hazırdır. Mövzuya keçək.
Yüksək səslə düşünmək
Sonra çay içməyə getdim. Bezdim bu misallardan 😀
Mövzu ailəsi
Mövzu ailəsi isti iplərin ən yaxşı nümunəsidir. Bu siniflər eyni zamanda müşahidə oluna bilən və müşahidəçi kimi çıxış edən bir növ hibriddir. Mövzu qaynar bir axın olduğundan, onun abunəliyi ləğv edilməlidir. Əsas üsullar haqqında danışırıqsa, bunlar:
növbəti - axına yeni məlumatların ötürülməsi
səhv - xəta və mövzunun dayandırılması
tam - ipin sonu
abunə olun - axına abunə olun
abunəni dayandırmaq - mövzudan çıxmaq
asObservable - müşahidəçiyə çevrilir
toPromise - sözə çevrilir
4 5 növ fənni ayırın.
Yüksək səslə düşünmək
Yayımda 4 dedim, amma bir də əlavə etdilər. Necə deyərlər, yaşa və öyrən.
Sadə Mövzu new Subject()- ən sadə fənlər növü. Parametrlər olmadan yaradılmışdır. Yalnız abunədən sonra gələn dəyərləri keçir.
DavranışMövzu new BehaviorSubject( defaultData<T> ) - mənim fikrimcə, mövzuların ən çox yayılmış növü. Giriş standart dəyəri alır. Abunə olarkən ötürülən son buraxılışın məlumatlarını həmişə saxlayır. Bu sinif həmçinin axının cari dəyərini qaytaran faydalı dəyər metoduna malikdir.
ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) - İstəyə görə, ilk arqument kimi özündə saxlayacağı dəyərlər buferinin ölçüsünü, ikinci dəfə isə dəyişikliklərə ehtiyac duyduğumuz zaman götürə bilər.
asyncsubject new AsyncSubject() - abunə zamanı heç nə baş vermir və dəyər yalnız tamamlandıqdan sonra qaytarılacaq. Axının yalnız sonuncu dəyəri qaytarılacaq.
WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - Sənədləşmə bu barədə susur və mən özüm bunu ilk dəfədir görürəm. Kim bilir nə edir, yaz, əlavə edəcəyik.
vay. Yaxşı, bu gün demək istədiyim hər şeyi nəzərdən keçirdik. Ümid edirəm bu məlumat faydalı oldu. Siz Faydalı Məlumatlar sekmesinde ədəbiyyat siyahısını özünüz oxuya bilərsiniz.