Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Tere kõigile. Siin on Omelnitski Sergei. Hiljuti viisin läbi voogedastuse reaktiivprogrammeerimise kohta, kus rääkisin asünkroonsusest JavaScriptis. Täna tahaksin selle materjali kokku võtta.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Kuid enne, kui alustame põhiteemat, peame tegema sissejuhatuse. Niisiis, alustame määratlemisest: mis on virn ja järjekord?
Virn on kogum, mille elemendid saavad põhinedes "viimane sisse, first out" LIFO
Järjekord on kogum, mille elemendid saavad põhinedes "esimene sisse, esimene välja" FIFO

Selge, jätkame.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

JavaScript on ühetine programmeerimiskeel. See tähendab, et seal on ainult üks teostusniit ja üks virn, kuhu funktsioonid pandakse täitmiseks järjekorda. Seega saab JavaScript korraga teostada ainult ühte operatsiooni, teised operatsioonid peavad oma järjekorda ootama virnas, kuni neid kutsutakse.

Kutsumisvirn on andmestruktuur, mis lihtsustatult öeldes salvestab teavet selle kohta, kus me programmis asume. Kui me liigume funktsiooni, paneme selle kirje virna ülempoolsesse ossa. Kui me funktsioonist tagasi tuleme, tõmbame virnast kõige ülemise elemendi ja jõuame sinna, kust seda funktsiooni kutsuti. See on kõik, mida virn suudab. Nüüd on väga huvitav küsimus. Kuidas siis töötab asünkroonsus JavaScriptis?

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Tegelikult on peale virna brauserites olemas ka eriline järjekord, kus töötab nn WebAPI. Funktsioonid sellest järjekorrast täidetakse järjestikku ainult pärast seda, kui virn on täielikult tühjaks tehtud. Alles pärast seda paigutatakse need järjekorrast virna täitmiseks. Kui virnas on vähemalt üks element, siis nad ei saa virna siseneda. Just seetõttu on funktsioonide kutsumine ajavahemiku järgi tihti ajaliselt ebatäpne, kuna funktsioon ei saa pääseda järjekorrast virna, kuni see on täis.

Vaatame järgmine näide ja tegeleme selle samm-sammult "teostamisega". Samuti uurime, mis süsteemis sellega toimub.

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

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

1) Seni ei toimu midagi. Brauseri konsool on puhas, kutsumisvirn on tühi.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

2) Siis lisatakse käsk console.log(‘Hi’) kutsumisvirna.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

3) Ja see täidetakse

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

4) Seejärel eemaldatakse console.log(‘Hi’) kutsumissteigist.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

5) Nüüd liikume käsu setTimeout(function cb1() {… }) juurde. See lisatakse kutsumissteigi.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

6) Käsk setTimeout(function cb1() {… }) täidetakse. Brauser loob taimeri, mis on osa Web API-st. See hakkab ajaloos tagasi lugema.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

7) Käsk setTimeout(function cb1() {… }) lõpetab töö ja eemaldatakse kutsumissteigist.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

8) Käsk console.log(‘Bye’) lisatakse kutsumissteigi.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

9) Käsk console.log(‘Bye’) täidetakse.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

10) Käsk console.log(‘Bye’) eemaldatakse kutsumissteigist.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

11) Pärast vähemalt 5000 ms möödumist, taimer lõpetab töö ja paneb kolmanda argumendi cb1 tagumise järjekorda.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

12) Ürituste tsükkel võtab funktsiooni cb1 tagumisest järjekorrast ja paneb selle kutsumissteigi.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

13) Funktsioon cb1 täidetakse ja lisab console.log(‘cb1’) kutsumissteigi.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

14) Käsk console.log(‘cb1’) täidetakse.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

15) Käsk console.log(‘cb1’) eemaldatakse kutsumissteigist.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

16) Funktsioon cb1 eemaldatakse kutsumissteigist.

Vaadakem näidet dünaamikas:

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Noh, oleme uurinud, kuidas JavaScriptis asünkroonsus töötab. Nüüd räägime lühidalt asünkroonse koodi arengust.

Asünkroonse koodi areng.

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);
                    })
                })
            })
        })
    })
});

Asünkroonne programmeerimine, nagu me seda JavaScripts tunneme, saab teostada ainult funktsioonidega. Need võivad olla edastatud nagu iga teine muutuja teistele funktsioonidele. Nii sündisid tagasikutsed. Ja see on lahe, lõbus ja energiline, kuni see muutub kurvaks, igavaks ja masendavaks. Miks? Lihtsalt:

  • Koodi keerukuse suurenemisega muutub projekt kiiresti arusaamatuks mitmekordselt sisestatud plokkideks — "callback hell".
  • Vea käsitlemine võib kergesti ununeda.
  • Väljendeid ei saa tagastada return-iga.

Promises'i tulekuga paranes olukord veidi.

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

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

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

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

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

  • Tekkis lubade ahel, mis parandas koodi loetavust.
  • Ilmus eraldi viga püüdmise meetod.
  • Ilmnes paralleelse täitmise võimalus Promise.all abil
  • Sisemise asünkroonsuse saame lahendada async/await abil

Kuid lubadusel on oma piirangud. Näiteks lubadust ei saa tühistada ilma eriliste pingutusteta, ja mis veelgi olulisem - see töötab ühe väärtusega.

Noh, oleme sujuvalt jõudnud reaktiivse programmeerimise juurde. Kas olete väsinud? Õnneks on võimalik minna tee keetmiseks, mõelda ja naasta lugema edasi. Aga mina jätkan.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Reaktiivne programmeerimine— programmiskeem, mis keskendub andmevoogudele ja muutuste levitamisele. Vaatame lähemalt, mis on andmevoog.

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

const eventsArray = [];

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

Kujutame ette, et meil on sisestusväli. Loome massiivi ja salvestame iga input'i keyup sündmuse meie massiivi. Samuti tahan märkida, et meie massiiv on järjestatud ajaliselt, st hilisemate sündmuste indeks on suurem kui varasemate indeks. Selline massiiv esindab lihtsustatud andmevoogu, kuid see pole veel voog. Et seda massiivi saaks julgesti nimetada vooguks, peab see suutma mingil viisil teavitada tellijaid, et sinna on tulnud uusi andmeid. Seega jõudsime andmevoo määratlemiseni.

Andmevoog

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

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

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Voog— on ajaliselt järjestatud andmemassiiv, mis võib teavitada andmete muudatustest. Kujutage nüüd ette, kui mugav on kirjutada koodi, kus ühe tegevuse jaoks tuleb kutsuda mitu sündmust erinevates koodilõikudes. Me lihtsalt tellime voogu ja see teavitab meid automaatselt, kui muudatused toimuvad. Ja seda suudab RxJs teek.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

RxJS — on teek asünkroonsete ja sündmustel põhinevate programmide jaoks, kasutades jälgitavaid järjestusi. Teek pakub põhityüpi Observable, mitu abistavat tüüpi (Observer, Schedulers, Subjects) ja sündmuste töötlemise operaatorid nagu kogumite puhul (map, filter, reduce, every ja sarnased JavaScripti massiivist).

Vaatame põhikontseptsioone, mis selles teegis on.

Observable, Observer, Producer

Observable on esimene põhiklass, millega me tutvume. See klass sisaldab RxJs rakenduse põhijõudu. See on seotud jälgitava vooguga, millele saab registreeruda meetodi subscribe kaudu.

Observable'is on rakendatud abimehhanism värskenduste loomiseks, nii öeldakse Observer. Kõiki Observer'i väärtusi,tootjat nimetatakse Producer. See võib olla massiiv, iteratsioon, web soket, mingi sündmus jne. Seega võib öelda, et observable on vahendaja Producer'i ja Observer'i vahel.

Observable töötleb kolme tüüpi üritusi Observer'ile:

  • next – uued andmed
  • error – viga, kui jada lõppes erandi tõttu. see sündmus eeldab ka jada lõpetamist.
  • complete — signaal jada lõpetamiseks. See tähendab, et uusi andmeid enam ei tule.

Vaadakem demo:

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Alustame väärtuste 1, 2, 3 töötlemisest, ja pärast 1 sekundit saame 4 ning lõpetame meie voolu.

Mõtisklen

Ja siis mõistsin, et rääkimine oli huvitavam kui sellest kirjutamine. 😀

Subscription

Kui me registreerume voogule, loome uue klassi subscription, mis võimaldab meil tühistada tellimuse meetodi unsubscribekaudu. Samuti saame tellimusi rühmitada meetodi kaudu add. Ja on mõistetav, et saame vooge eraldada meetodi abil eemalda. Meetodid add ja remove võtavad sisse teise tellimuse. Tahaksin märkida, et kui me tühistame tellimuse, siis tühistame me ka kõik alamtellimused just nagu oleksime neile käskinud meetodit unsubscribe. Jätkame.

Vood tüübid

HOTCOLD
Producer luuakse observable'ist väljaspoolProducer luuakse observable'i sees
Andmed edastatakse observable'i loomise hetkelAndmed edastatakse tellimise hetkel
Tuleb lisaloogika tühistamiseksVoo lõpetab iseennast
Kasutab ühte-korra-mitmele seondumistKasutab ühe-korra-ühele seondumist
Kõik tellimused omavad ühte väärtustTellimused on sõltumatud
Andmed võivad kaduda, kui tellimust ei oleTaaskäideldakse kõiki voolu väärtusi uus tellimus

Kui tuua analoogia, siis kujutaksin kuuma voolu nagu filmi kinos. Millal sa kohale jõudsid, sellest hetkest alates hakad vaatama. Külma voolu võiksin võrrelda tehnilise toe kõnega. Iga helistaja kuulab vastusautomaatide salvestust algusest lõpuni, kuid saad telefonikõne katkestada meetodiga unsubscribe.

Tahaksin märkida, et eksisteerivad ka nn soojad vood (seda määratlust kohtasin äärmiselt harva ja ainult välisriikide kogukondades) — see on voog, mis transformeerub külmast voost kuumaks. Küsitav on, kus seda kasutada)) Toon näite praktikast.

Töötan angulaariga. See kasutab aktiivselt rxjs-i. Serverist andmete saamiseks ootan külma voogu ja kasutan seda šabloonis koos asyncPipe'iga. Kui kasutan seda pipa mitu korda, siis naastes külma voolu määratlemise juurde, taotleb iga pipa andmeid serverist, mis on pehmelt öeldes kummaline. Kui aga muudan külma voogu soojaks, siis toimub päring üks kord.

Üldiselt on voogude tüüpide mõistmine algajatele piisavalt keeruline, kuid oluline.

Operaatorid

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

Operaatorid pakuvad meile laiendatud võimalusi voogudega töötamiseks. Need aitavad kontrollida Observable'is toimuvaid sündmusi. Käsitleme mõnda kõige populaarsemat, kuid operaatoritega saab lähemalt tutvuda kasuliku info lingil.

Operaatorid — of

Alustame abistava operaatori of'iga. See loob Observable'i lihtsast väärtusest.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Operaatorid — filter

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Filteroperaator filter, nagu nimi ütleb, filtreerib voolu signaali. Kui operaator tagastab tõe, siis lastakse see edasi.

Operaatorid — take

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

take — Võtab vastu emitatsioonide arvu, pärast mida lõpetab voo.

Operaatorid — debounceTime

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

debounceTime — viskab kõrvale emitatsioonid, mis satuvad määratud aja vahemikku väljundite vahel — pärast ajavahemiku möödumist emititakse viimane väärtus.

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

Observable.create((observer) => {
  let i = 1;
  observer.next(i++);
  // Emitime väärtuse iga 1000 ms järel
  setInterval(() => {
    observer.next(i++)
  }, 1000);

  // Emitime väärtuse iga 1500 ms järel
  setInterval(() => {
    observer.next(i++)
  }, 1500);
}).pipe(
  debounceTime(700),  // Ootame 700 ms väärtuse enne töötlemist
  take(3)
);  

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Operaatorid — takeWhile

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Emitib väärtusi, kuni takeWhile ei tagasta false, pärast mida loobub voost.

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

Observable.create((observer) => {
  let i = 1;
  observer.next(i++);
  // Emit value every 1000ms
  setInterval(() => {
    observer.next(i++)
  }, 1000);
}).pipe(
  takeWhile(producer => producer < 5)
);  

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Operators — combineLatest

Kombineeritud operaator combineLatest sarnaneb promise.all-iga. See ühendab mitmed vood ühte. Pärast seda, kui iga voog on teinud vähemalt ühe emit'i, saame viimasest väärtused igaühe kohta massiivina. Edasi, pärast mida tahes ühte emit'i ühendatud voogudest, annab see uusi väärtusi.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

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

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Emit value every 1000ms
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
});

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Emit value every 750ms
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
});

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

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Operators — zip

Zip ootab väärtust igast voost ja moodustab massiivi nende väärtuste põhjal. Kui väärtus ei tule üheltki voost, siis gruppi ei moodustata.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

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

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Emit value every 1000ms
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
});

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Emit value every 750
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
});

const observer_3 = Observable.create((observer) => {
  let i = 1;
  // Emit value every 500
  setInterval(() => {
    observer.next('c: ' + i++);
  }, 500);
});

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

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Operators — forkJoin

forkJoin ühendab ka vood, kuid emit'ib väärtuse ainult siis, kui kõik vood on lõpetatud (complete).

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

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

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Emit value every 1000ms
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
}).pipe(take(3));

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Emit value every 750
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
}).pipe(take(5));

const observer_3 = Observable.create((observer) => {
  let i = 1;
  // Emit value every 500
  setInterval(() => {
    observer.next('c: ' + i++);
  }, 500);
}).pipe(take(4));

forkJoin(observer_1, observer_2, observer_3);

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Operators — map

Muutmisoperaator map muudab emit'i väärtuse uueks.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

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

Observable.create((observer) => {
  let i = 1;
  // Emit value every 1000ms
  setInterval(() => {
    observer.next(i++);
  }, 1000);
}).pipe(
  map(x => x * 10),
  take(3)
);

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Operators – share, tap

Operator tap võimaldab teha kergelt kõrvalisi efekte, s.t. teisi tegevusi, mis ei mõjuta järjestust.

Abiooperator share suudab külma voolu muuta kuumaks.

Asünkroonne programmeerimine JavaScriptis. (Tagasiside, Lubadus, RxJs)

Oleme operaatoritega lõpetanud. Liigume subjektide juurde.

Mõtisklen

Ja siis läksin ma teed jooma. Need näited väsitasid mind 😀

Subjektide perekond

Subjektide perekond on ilmekas näide kuumadest voogudest. Need klassid on teatud hübriid, mis toimivad samal ajal nii observable kui observerina. Kuna subjekt on kuum voog, tuleb sellelt maha registreerida. Peamised meetodid on:

  • next – andmete edastamine voogu
  • error – voogu lõpetamine ja viga
  • complete – voogu lõpetamine
  • subscribe – voogu registreerimine
  • unsubscribe – voogust maha registreerimine
  • asObservable – muudame vaatlejaks
  • toPromise – muudab lubaduseks

Erakordselt on 4-5 tüüpi subjekte.

Mõtisklen

Streamil rääkisin 4, aga selgus, et nad on ühe veel lisanud. Nagu öeldakse, elad ja õpid.

Lihtne Subject new Subject()– kõige lihtsam tüüp subjekte. Loob ilma parameetriteta. Edastab väärtusi, mis saabuvad vaid pärast registreerimist.

BehaviorSubject new BehaviorSubject(defaultData<T>) – minu arvates kõige levinum tüüp subjekte. Võtab sisendiks vaikeväärtuse. Hoidub alati viimase edastuse andmetest, mille ta edastab registreerimise ajal. Sellel klassil on samuti kasulik meetod value, mis tagastab voolu praeguse väärtuse.

ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) – Võtab sisendiks valikuliselt esmase argumendina väärtuste puhvri suuruse, mille ta enda sisse salvestab, ja teise argumentina aja, mille jooksul on meil muudatused vajalikud.

AsyncSubject new AsyncSubject() – registreerimisel ei toimu midagi, ja väärtus naastes ainult siis, kui voog on lõpetatud. Tagastatakse ainult voolu viimane väärtus.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) – Dokumentatsioon selle kohta ei räägi ja näen seda esmakordselt. Kes teab, mida see teeb, kirjutage, täiendame.

Ohh. Noh, oleme nüüd läbinud kõik, mida tahtsin täna jagada. Loodan, et see teave oli kasulik. Iseseisvalt saab kirjanduse nimekirjaga tutvuda kasulike info sektsioonist.

Kasulik teave

Allikas: habr.com

Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster