Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Tere kõigile. Võtke ühendust Sergei Omelnitskiga. Mitte nii kaua aega tagasi võõrustasin reaktiivse programmeerimise voogu, kus rääkisin JavaScripti asünkroonist. Täna tahaksin selle materjali kokku võtta.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Kuid enne põhimaterjaliga alustamist peame tegema sissejuhatuse. Alustame definitsioonidega: mis on virn ja järjekord?

Virna on kogumik, mille elemendid otsitakse LIFO põhimõttel "viimane sisse, esimene välja".

Pöörake on kogum, mille elemendid saadakse põhimõttel (“first in, first out” FIFO

Olgu, jätkame.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

JavaScript on ühe lõimega programmeerimiskeel. See tähendab, et sellel on ainult üks täitmise lõim ja üks virn, kus funktsioonid on täitmiseks järjekorras. Seetõttu saab JavaScript teha ainult ühe toimingu korraga, samas kui teised toimingud ootavad pinus oma järjekorda, kuni neid kutsutakse.

Kõnede virn on andmestruktuur, mis lihtsamalt öeldes salvestab informatsiooni selle koha kohta programmis, kus me asume. Kui hüppame funktsiooni juurde, lükkame selle sisestuse virna ülaossa. Funktsioonist naastes tõstame virnast ülemise elemendi ja jõuame sinna, kust selle funktsiooni kutsusime. See on kõik, mida virn teha saab. Ja nüüd väga huvitav küsimus. Kuidas siis asünkroonsus JavaScriptis töötab?

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Tegelikult on brauserites lisaks virnale ka spetsiaalne järjekord nn WebAPI-ga töötamiseks. Funktsioonid sellest järjekorrast käivitatakse järjekorras alles pärast pinu täielikku tühjendamist. Alles pärast seda paigutatakse need täitmiseks järjekorrast virna. Kui virna peal on parasjagu vähemalt üks element, siis need virna peale ei pääse. Just seetõttu on funktsioonide ajalõpu järgi kutsumine sageli ajaliselt ebatäpne, kuna funktsioon ei pääse järjekorrast virna, kui see on täis.

Vaatame järgmist näidet ja vaatame seda samm-sammult läbi. Vaatame ka, mis süsteemis toimub.

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

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

1) Siiani pole midagi juhtunud. Brauseri konsool on puhas, kõnepinn on tühi.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

2) Seejärel lisatakse kõnepinku käsk console.log('Hi').

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

3) Ja see on täidetud

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

4) Seejärel eemaldatakse console.log('Hi') kõnepinust.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

5) Liigume nüüd käsu setTimeout(function cb1() {… }) juurde. See lisatakse kõnevirna.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

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

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

7) Käsk setTimeout(function cb1() {… }) on oma töö lõpetanud ja eemaldatakse kõnepinust.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

8) Console.log('Bye') käsk lisatakse kõne pinu.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

9) Käsk console.log('Bye') käivitatakse.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

10) Käsk console.log('Bye') eemaldatakse kõnepinust.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

11) Pärast vähemalt 5000 ms möödumist taimer lõpeb ja lisab cb1 tagasihelistamise tagasihelistamise järjekorda.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

12) Sündmustsükkel võtab tagasihelistamisjärjekorrast funktsiooni cb1 ja surub selle kõnepinku.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

13) Funktsioon cb1 käivitatakse ja lisab console.log('cb1') kõne pinu.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

14) Käsk console.log('cb1') käivitatakse.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

15) Käsk console.log('cb1') eemaldatakse kõnepinust.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

16) Funktsioon cb1 eemaldatakse kõne pinust.

Vaatame näidet dünaamikast:

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Vaatasime, kuidas asünkroonsust JavaScriptis rakendatakse. Räägime nüüd 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ünkroonset programmeerimist, nagu me seda JavaScriptis teame, saab teha ainult funktsioonidega. Neid saab edastada nagu kõiki teisi muutujaid teistele funktsioonidele. Nii sündisid tagasihelistamised. Ja see on lahe, lõbus ja tuline, kuni see muutub kurbuseks, melanhooliaks ja kurbuseks. Miks? Jah, see on lihtne:

  • Koodi keerukuse kasvades muutub projekt kiiresti mitmeks varjatuks pesastatud plokiks - "tagasihelistamispõrguks".
  • Vigade käsitlemine võib kergesti tähelepanuta jääda.
  • Sa ei saa avaldisi tagastada koos return-iga.

Promise tulekuga on olukord veidi paremaks läinud.

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

  • Ilmusid lubadusketid, mis parandasid koodi loetavust
  • Eraldi oli vigade pealtkuulamise meetod
  • Paralleelne täitmine lisatud Promise.all-iga
  • Pesastatud asünkrooniat saame lahendada käsuga async/await

Kuid lubadusel on omad piirangud. Näiteks lubadust, ilma tamburiiniga tantsimata, ei saa tühistada ja mis kõige tähtsam, see töötab ühe väärtusega.

Noh, siin me läheneme sujuvalt reaktiivsele programmeerimisele. Väsinud? Hea on see, et võite minna kajakaid pruulima, ajurünnakut tegema ja naasta, et rohkem lugeda. Ja ma jätkan.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Reaktiivne programmeerimine - programmeerimisparadigma, mis keskendub andmevoogudele ja muutuste levikule. 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 iga sisendsündmuse võtmeklahvi jaoks salvestame sündmuse oma massiivi. Samas märgin ära, et meie massiiv on sorteeritud aja järgi, s.t. hilisemate sündmuste indeks on suurem kui varasemate sündmuste indeks. Selline massiiv on lihtsustatud andmevoo mudel, kuid see pole veel voog. Selleks, et seda massiivi saaks ohutult vooks nimetada, peab see suutma tellijaid kuidagi teavitada uute andmete saabumisest. Nii jõuame voolu definitsioonini.

Andmevoog

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

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

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Voolu on aja järgi sorteeritud andmete massiiv, mis võib näidata, et andmed on muutunud. Kujutage nüüd ette, kui mugav on kirjutada koodi, milles peate ühe toimingu jaoks käivitama mitu sündmust koodi erinevates osades. Tellime lihtsalt voo ja see annab meile teada, kui muudatused toimuvad. Ja RxJ teek saab seda teha.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

RxJS on teek asünkroonsete ja sündmustepõhiste programmidega töötamiseks, kasutades jälgitavaid järjestusi. Raamatukogu pakub peamist tüüpi Vaadeldav, mitut tüüpi abistajaid (Vaatlejad, planeerijad, subjektid) ja operaatorid nii sündmuste kui ka kogudega töötamiseks (kaardistada, filtreerida, vähendada, iga ja sarnased JavaScripti massiivist).

Saame aru selle raamatukogu põhikontseptsioonidest.

Vaadeldav, vaatleja, produtsent

Vaadeldav on esimene baastüüp, mida me vaatame. See klass sisaldab põhiosa RxJ-de rakendamisest. See on seotud jälgitava vooga, mida saab tellida tellimismeetodi abil.

Observable rakendab uuenduste loomise abimehhanismi nn vaatama. Vaatleja väärtuste allikat nimetatakse Tootja. See võib olla massiiv, iteraator, veebipesa, mingi sündmus jne. Seega võime öelda, et vaadeldav on dirigent Tootja ja Vaatleja vahel.

Observable käsitleb kolme tüüpi vaatleja sündmusi:

  • järgmine – uued andmed
  • viga - viga, kui jada katkes erandi tõttu. see sündmus tähendab ka jada lõppu.
  • lõpetatud – signaal jada lõpu kohta. See tähendab, et uusi andmeid enam ei tule

Vaatame demo:

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Alguses töötleme väärtusi 1, 2, 3 ja 1 sekundi pärast. saame 4 ja lõpetame lõime.

Mõeldes valjusti

Ja siis sain aru, et sellest oli huvitavam rääkida kui kirjutada. 😀

Püsitellimus

Voo tellimisel loome uue klassi tellimine, mis annab meile võimaluse meetodi abil tellimusest loobuda lahkuda. Meetodi abil saame ka tellimusi rühmitada lisama. Noh, on loogiline, et saame lõimede rühmitamist kasutades kõrvaldama. Lisamise ja eemaldamise meetodid aktsepteerivad sisendiks teistsugust tellimust. Tahaksin märkida, et tellimuse tühistamisel loobume kõigist alamtellimustest, nagu nimetaksid nad ka tellimuse tühistamise meetodit. Lase käia.

Voogude tüübid

KUUM
KÜLM

Tootja luuakse väljaspool vaadeldavat
Tootja luuakse vaadeldava sees

Andmed edastatakse jälgitava loomise ajal
Andmed esitatakse tellimise ajal.

Tellimusest loobumiseks on vaja rohkem loogikat
Lõim lõpeb iseenesest

Kasutab üks-mitmele suhet
Kasutab üks-ühele suhet

Kõikidel tellimustel on sama väärtus
Tellimused on sõltumatud

Abonemendi puudumisel võivad andmed kaduda
Väljastab uuesti kõik vooväärtused uue tellimuse jaoks

Kui tuua analoogia, siis ma kujutaksin ette kuuma voogu nagu filmis kinos. Mis ajahetkel sa tulid, sellest hetkest hakkasid sa vaatama. Ma võrdleksin külma voolu kutsumisega nendes. toetus. Iga helistaja kuulab automaatvastaja salvestust algusest lõpuni, kuid saate kõne katkestada, kasutades tellimuse tühistamist.

Tahan märkida, et on ka nn soojad ojad (sellist määratlust olen kohanud üliharva ja ainult välismaistes kogukondades) - see on oja, mis muundub külmast ojast kuumaks. Tekib küsimus - kus kasutada)) Toon näite praktikast.

Töötan Angulariga. Ta kasutab aktiivselt rxjs-i. Andmete serverisse jõudmiseks eeldan külma voogu ja kasutan seda voogu mallis asyncPipe'i abil. Kui ma seda toru mitu korda kasutan, siis külma voo definitsiooni juurde naastes küsib iga toru serverilt andmeid, mis on pehmelt öeldes kummaline. Ja kui ma muudan külma oja soojaks, siis taotlus toimub üks kord.

Üldiselt on voogude tüübi mõistmine algajatele üsna keeruline, kuid oluline.

Ettevõtjad

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

Operaatorid annavad meile võimaluse töötada voogudega. Need aitavad kontrollida vaadeldavas sündmusi. Vaatleme paari kõige populaarsemat ja rohkem teavet operaatorite kohta leiate kasuliku teabe linkidelt.

operaatorid

Alustame abioperaatoriga. See loob vaadeldava lihtsa väärtuse alusel.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Operaatorid-filter

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Filtrioperaator, nagu nimigi ütleb, filtreerib voosignaali. Kui operaator tagastab tõese, hüppab see edasi.

Operaatorid - võtke

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

võta – võtab väljade arvu väärtuse, mille järel voog lõpeb.

Operaatorid-debounceTime

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

debounceTime - jätab kõrvale väljastatud väärtused, mis jäävad väljundandmete vahel määratud ajavahemikku - pärast ajaintervalli möödumist väljastab viimane väärtus.

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

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Operaator-takeWhile

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Väljastab väärtusi, kuni takeWhile tagastab false, seejärel tühistab voo tellimuse.

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

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Operaatorid-kombaini Viimased

Kombineeritud operaator combiLatest on mõneti sarnane lubadusega.all. See ühendab mitu voogu üheks. Pärast seda, kui iga lõime on tekitanud vähemalt ühe kiirgamise, saame neist massiivina uusimad väärtused. Lisaks annab see pärast kombineeritud voogude mis tahes kiirgamist uusi väärtusi.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

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

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Operaatorid-zip

Zip – ootab igast voost väärtust ja moodustab nende väärtuste põhjal massiivi. Kui väärtus ei pärine ühestki lõimest, siis rühma ei moodustata.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

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

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Operaatorid – forkJoin

forkJoin ühendab ka lõime, kuid väljastab väärtuse alles siis, kui kõik lõimed on lõpetatud.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

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

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Operaatorid-kaart

Kaardi teisenduse operaator muudab emiteeritud väärtuse uueks.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

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

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Operaatorid – jagage, puudutage

Kraanioperaator võimaldab teil teha kõrvalmõjusid, st mis tahes toiminguid, mis ei mõjuta järjestust.

Jagamisteenuse operaator saab muuta külma voo kuumaks vooks.

Asünkroonne programmeerimine JavaScriptis (tagasihelistamine, lubadus, RxJs)

Operaatorid on tehtud. Liigume edasi teema juurde.

Mõeldes valjusti

Ja siis läksin teed jooma. Olen väsinud nendest näidetest 😀

Teema perekond

Teemapere on kuumade lõimede ehe näide. Need klassid on omamoodi hübriid, mis toimivad nii vaatlejana kui ka vaatlejana. Kuna teema on kuum voog, tuleb selle tellimus tühistada. Kui me räägime peamistest meetoditest, siis need on järgmised:

  • järgmine - uute andmete edastamine voogu
  • viga - viga ja lõime lõpetamine
  • täielik - niidi lõpp
  • tellida – voo tellimine
  • unsubscribe – tühista voo tellimine
  • asObservable – muutuda vaatlejaks
  • toPromise – muundub lubaduseks

Eraldage 4 5 tüüpi aineid.

Mõeldes valjusti

Ütlesin voos 4, aga selgus, et nad lisasid veel ühe. Nagu öeldakse, ela ja õpi.

Lihtne teema new Subject()- kõige lihtsamad ained. Loodud ilma parameetriteta. Annab edasi väärtused, mis tulid alles pärast tellimust.

BehaviorSubject new BehaviorSubject( defaultData<T> ) - minu arvates kõige levinum ainetüüp. Sisend võtab vaikeväärtuse. Salvestab alati viimase numbri andmed, mis tellimisel edastatakse. Sellel klassil on ka kasulik väärtusmeetod, mis tagastab voo praeguse väärtuse.

ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) - Valikuliselt võib esimese argumendina võtta väärtuste puhvri suuruse, mille see ise salvestab, ja teise korra, mille jooksul vajame muudatusi.

asünkroonsubjekt new AsyncSubject() - tellimisel ei juhtu midagi ja väärtus tagastatakse alles siis, kui see on lõpetatud. Tagastada saab ainult voo viimane väärtus.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - Dokumentatsioon sellest vaikib ja ma ise näen seda esimest korda. Kes teab millega tegeleb, kirjutage, lisame.

Pheh. Noh, oleme kaalunud kõike, mida ma täna öelda tahtsin. Loodetavasti oli see teave abiks. Kirjanduse loendit saate ise lugeda vahekaardil Kasulik teave.

kasulikku teavet

Allikas: www.habr.com

Lisa kommentaar