Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Pozdravljeni vsi skupaj. Sergey Omelnitsky je v stiku. Nedolgo nazaj sem gostil tok o reaktivnem programiranju, kjer sem govoril o asinhronosti v JavaScriptu. Danes bi rad naredil zapiske o tem gradivu.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Toda preden začnemo z glavnim gradivom, moramo narediti uvodno opombo. Pa začnimo z definicijami: kaj je sklad in čakalna vrsta?

Stack je zbirka, katere elementi so pridobljeni na podlagi LIFO zadnji vstop, prvi ven

Čakalna vrsta je zbirka, katere elementi so pridobljeni po načelu FIFO prvi vstop, prvi ven

V redu, nadaljujmo.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

JavaScript je enoniten programski jezik. To pomeni, da obstaja samo ena nit izvajanja in en sklad, na katerem so funkcije v čakalni vrsti za izvajanje. Zato lahko JavaScript izvaja le eno operacijo naenkrat, medtem ko bodo druge operacije čakale na vrsto na skladu, dokler niso poklicane.

Sklad klicev je podatkovna struktura, ki preprosto povedano beleži informacije o mestu v programu, kjer se nahajamo. Če preidemo v funkcijo, potisnemo njen vnos na vrh sklada. Ko se vrnemo iz funkcije, odstranimo najvišji element iz sklada in končamo nazaj, kjer smo poklicali funkcijo. To je vse, kar sklad zmore. In zdaj eno izjemno zanimivo vprašanje. Kako potem deluje asinhronost v Javascriptu?

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Pravzaprav imajo brskalniki poleg sklada posebno čakalno vrsto za delo s tako imenovanim WebAPI. Funkcije v tej čakalni vrsti se bodo izvajale po vrstnem redu šele, ko bo sklad popolnoma počiščen. Šele po tem so potisnjeni iz čakalne vrste na sklad za izvedbo. Če je trenutno na skladu vsaj en element, ga ni mogoče dodati v sklad. Ravno zaradi tega klicanje funkcij po časovni omejitvi pogosto ni časovno natančno, saj funkcija ne more priti iz čakalne vrste v sklad, medtem ko je poln.

Oglejmo si naslednji primer in začnimo z njegovo izvedbo po korakih. Poglejmo tudi, kaj se dogaja v sistemu.

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

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

1) Nič se še ne dogaja. Konzola brskalnika je jasna, sklad klicev je prazen.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

2) Nato se v sklad klicev doda ukaz console.log('Hi').

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

3) In izpolnjeno je

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

4) Nato se console.log('Hi') odstrani iz sklada klicev.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

5) Zdaj pojdite na ukaz setTimeout(function cb1() {… }). Doda se v sklad klicev.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

6) Izvede se ukaz setTimeout(function cb1() {… }). Brskalnik ustvari časovnik, ki je del spletnega API-ja. Izvedel bo odštevanje.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

7) Ukaz setTimeout(function cb1() {... }) je končal svoje delo in je odstranjen iz sklada klicev.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

8) Ukaz console.log('Bye') je dodan v sklad klicev.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

9) Izvede se ukaz console.log('Bye').

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

10) Ukaz console.log('Bye') je odstranjen iz sklada klicev.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

11) Po preteku vsaj 5000 ms se časovnik prekine in postavi povratni klic cb1 v čakalno vrsto za povratni klic.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

12) Dogodkovna zanka vzame funkcijo cb1 iz čakalne vrste za povratne klice in jo postavi v sklad klicev.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

13) Funkcija cb1 se izvede in doda console.log('cb1') v sklad klicev.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

14) Izvede se ukaz console.log('cb1').

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

15) Ukaz console.log('cb1') je odstranjen iz sklada klicev.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

16) Funkcija cb1 je odstranjena iz sklada klicev.

Poglejmo primer v dinamiki:

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

No, pogledali smo, kako je asinhronost implementirana v JavaScript. Zdaj pa se na kratko pogovorimo o razvoju asinhrone kode.

Razvoj asinhrone kode.

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

Asinhrono programiranje, kot ga poznamo v JavaScriptu, je mogoče izvajati samo s funkcijami. Lahko jih posredujete kot katero koli drugo spremenljivko drugim funkcijam. Tako so se rodili povratni klici. In je kul, zabavno in igrivo, dokler se ne spremeni v žalost, melanholijo in žalost. Zakaj? Preprosto je:

  • Ko se kompleksnost kode poveča, se projekt hitro spremeni v nejasne, večkrat ugnezdene bloke - "pekel povratnega klica".
  • Obravnavo napak je lahko enostavno spregledati.
  • Izrazov ne morete vrniti z return.

S prihodom Promisea se je situacija malo izboljšala.

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

  • Pojavile so se verige obljub, ki so izboljšale berljivost kode
  • Pojavila se je posebna metoda za lovljenje napak
  • Dodana možnost vzporednega izvajanja z uporabo Promise.all
  • Ugnezdeno asinhronost lahko rešimo z async/await

Toda obljube imajo svoje omejitve. Na primer, obljube ni mogoče preklicati brez plesa s tamburino, in kar je najpomembneje, deluje z eno vrednostjo.

No, gladko smo pristopili k reaktivnemu programiranju. Utrujeni? No, na srečo si lahko greš skuhat čaj, razmisliš o tem in se vrneš, da prebereš več. In bom nadaljeval.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Reaktivno programiranje je programska paradigma, osredotočena na tokove podatkov in širjenje sprememb. Oglejmo si podrobneje, kaj je tok podatkov.

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

const eventsArray = [];

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

Predstavljajmo si, da imamo vnosno polje. Ustvarjamo matriko in za vsako tipko vnosnega dogodka bomo dogodek shranili v našo matriko. Hkrati bi rad opozoril, da je naš niz razvrščen po času, tj. indeks kasnejših dogodkov je večji od indeksa prejšnjih. Takšno polje je poenostavljen model podatkovnega toka, vendar še ni tok. Da lahko ta niz varno imenujemo tok, mora biti sposoben nekako obvestiti naročnike, da so vanj prispeli novi podatki. Tako pridemo do definicije toka.

Tok podatkov

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

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

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Tok je niz podatkov, razvrščenih po času, ki lahko kažejo, da so se podatki spremenili. Zdaj pa si predstavljajte, kako priročno postane pisanje kode, pri kateri eno dejanje zahteva klic več dogodkov v različnih delih kode. Preprosto se naročimo na tok in ta nas bo obvestil, ko pride do sprememb. In knjižnica RxJs lahko to stori.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

RxJS je knjižnica za delo z asinhronimi in na dogodkih temelječimi programi, ki uporabljajo opazovana zaporedja. Knjižnica ponuja osnovno vrsto Opazno, več pomožnih vrst (Opazovalec, razporejevalci, subjekti) in operatorji za delo z dogodki kot z zbirkami (preslikati, filtrirati, zmanjšati, vsak in podobni iz JavaScript Array).

Razumejmo osnovne koncepte te knjižnice.

Opazljiv, opazovalec, proizvajalec

Observable je prvi osnovni tip, ki si ga bomo ogledali. Ta razred vsebuje glavni del implementacije RxJs. Povezan je z opazovanim tokom, na katerega se lahko naročite z metodo naročnine.

Observable implementira pomožni mehanizem za ustvarjanje posodobitev, tako imenovani Observer. Pokliče se vir vrednosti za opazovalca Producent. To je lahko niz, iterator, spletna vtičnica, nekakšen dogodek itd. Tako lahko rečemo, da je observable prevodnik med proizvajalcem in opazovalcem.

Observable obravnava tri vrste dogodkov Observer:

  • naprej – novi podatki
  • napaka – napaka, če se je zaporedje končalo zaradi izjeme. ta dogodek pomeni tudi zaključek zaporedja.
  • dokončano — signal o zaključku zaporedja. To pomeni, da novih podatkov ne bo več.

Oglejmo si demo:

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Na začetku bomo obdelali vrednosti 1, 2, 3 in po 1 s. dobili bomo 4 in zaključili naš tok.

Razmišljanje na glas

In potem sem spoznal, da je pripovedovanje bolj zanimivo kot pisanje o tem. 😀

Naročnina

Ko se naročimo na tok, ustvarimo nov razred naročninaki nam daje možnost odjave z metodo odjavi. Z metodo lahko naročnine tudi združujemo dodajte. No, logično je, da lahko niti razdružimo z uporabo odstrani. Metodi dodajanja in odstranjevanja sprejmeta drugo naročnino kot vnos. Rad bi opozoril, da ko se odjavljamo, odjavljamo vse podrejene naročnine, kot da bi poklicali metodo odjave. Kar daj.

Vrste tokov

HOT
HLADNO

Proizvajalec je ustvarjen zunaj vidnega
Proizvajalec je ustvarjen znotraj opazljivega

Podatki se prenesejo v trenutku, ko je opazovana ustvarjena
Podatki so posredovani ob naročnini

Za odjavo potrebujete dodatno logiko
Nit se konča sama od sebe

Uporablja razmerje ena proti mnogo
Uporablja razmerje ena proti ena

Vse naročnine imajo enak pomen
Naročnine so neodvisne

Podatki se lahko izgubijo, če nimate naročnine
Ponovno izda vse vrednosti toka za novo naročnino

Če navedem analogijo, bi si vroč tok predstavljal kot film v gledališču. Kdaj ste prišli, od tistega trenutka ste začeli gledati. Hladni tok bi primerjal s klicem v tehniki. podporo. Vsak klicatelj posluša posnetek glasovne pošte od začetka do konca, vendar lahko prekinete z odjavo.

Naj opozorim, da obstajajo tudi tako imenovani topli tokovi (na to definicijo sem naletel izjemno redko in le v tujih skupnostih) - to je tok, ki se iz hladnega toka spremeni v vročega. Postavlja se vprašanje - kje uporabiti)) Dal bom primer iz prakse.

Delam z Angularjem. Aktivno uporablja rxjs. Za prejemanje podatkov v strežnik pričakujem hladno nit in to nit uporabim v predlogi z uporabo asyncPipe. Če večkrat uporabim to cev, potem, če se vrnem k definiciji hladnega toka, bo vsaka cev zahtevala podatke od strežnika, kar je milo rečeno čudno. In če pretvorim hladen tok v topel, se bo zahteva zgodila enkrat.

Na splošno je razumevanje vrste tokov za začetnike precej težko, vendar pomembno.

Izvajalci

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

Operaterji nam omogočajo, da razširimo našo sposobnost dela s tokovi. Pomagajo nadzorovati dogodke, ki se dogajajo v Observable. Ogledali si bomo nekaj najbolj priljubljenih, več podrobnosti o operaterjih pa lahko najdete na povezavah v uporabnih informacijah.

Operaterji - of

Začnimo s pomožnim operatorjem. Ustvari Observable na podlagi preproste vrednosti.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Operaterji - filter

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Operater filtra, kot že ime pove, filtrira signal toka. Če operator vrne true, preskoči naprej.

Operaterji - vzemite

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

take — vzame vrednost števila oddajnikov, po kateri se nit konča.

Operatorji - debounceTime

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

debounceTime - zavrže oddane vrednosti, ki spadajo v določen časovni interval med izhodi - po preteku časovnega intervala odda zadnjo vrednost.

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

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Operaterji - takeWhile

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Oddaja vrednosti, dokler takeWhile ne vrne false, nato pa se odjavi iz niti.

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

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Operaterji - combineLatest

Operator combineLatest je nekoliko podoben promise.all. Združuje več niti v eno. Ko vsaka nit naredi vsaj eno emisijo, dobimo najnovejše vrednosti od vsake v obliki niza. Nadalje bo po kakršni koli emisiji iz združenih tokov dal nove vrednosti.

Asinhrono programiranje v JavaScriptu (Callback, Promise, 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));

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Operaterji - zip

Zip – čaka na vrednost iz vsake niti in na podlagi teh vrednosti oblikuje matriko. Če vrednost ne izvira iz nobene niti, potem skupina ne bo oblikovana.

Asinhrono programiranje v JavaScriptu (Callback, Promise, 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));

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Operatorji - forkJoin

forkJoin prav tako združuje niti, vendar odda vrednost šele, ko so vse niti dokončane.

Asinhrono programiranje v JavaScriptu (Callback, Promise, 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);

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Operaterji - zemljevid

Operator transformacije zemljevida pretvori vrednost emiterja v novo.

Asinhrono programiranje v JavaScriptu (Callback, Promise, 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)
);

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Operaterji – delite, tapnite

Operater pipe vam omogoča stranske učinke, to je vsa dejanja, ki ne vplivajo na zaporedje.

Operater delniškega komunalnega podjetja lahko hladen tok spremeni v vročega.

Asinhrono programiranje v JavaScriptu (Callback, Promise, RxJs)

Končali smo z operaterji. Preidimo na Zadevo.

Razmišljanje na glas

In potem sem šla spit čaj. Naveličana sem teh primerov 😀

Zadevna družina

Zadevna družina je odličen primer vročih tokov. Ti razredi so neke vrste hibridi, ki delujejo hkrati kot opazovalci in opazovalci. Ker je tema vroča tema, se je potrebno odjaviti od nje. Če govorimo o glavnih metodah, potem so to:

  • naprej – prenos novih podatkov v tok
  • napaka – napaka in prekinitev niti
  • dokončanje – zaključek niti
  • subscribe – naročite se na tok
  • unsubscribe – odjava od toka
  • asObservable – spremenite se v opazovalca
  • toPromise – spremeni se v obljubo

Obstaja 4 5 vrst predmetov.

Razmišljanje na glas

V toku so se pogovarjale 4 osebe, a izkazalo se je, da so dodale še eno. Kot pravijo, živi in ​​se uči.

Enostavna zadeva new Subject()– najpreprostejši tip predmetov. Ustvarjeno brez parametrov. Posreduje vrednosti, prejete samo po naročnini.

BehaviorSubject new BehaviorSubject( defaultData<T> ) – po mojem mnenju najpogostejši predmet. Vnos ima privzeto vrednost. Vedno shrani podatke zadnje številke, ki se posredujejo ob naročanju. Ta razred ima tudi metodo uporabne vrednosti, ki vrne trenutno vrednost toka.

ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) — Vnos lahko po želji vzame kot prvi argument velikost vmesnega pomnilnika vrednosti, ki jih bo shranil vase, kot drugi pa čas, v katerem potrebujemo spremembe.

AsyncSubject new AsyncSubject() — med naročanjem se ne zgodi nič in vrednost bo vrnjena šele, ko je dokončana. Vrnjena bo samo zadnja vrednost toka.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) — Dokumentacija o njem molči in jaz ga vidim prvič. Če kdo ve, kaj počne, naj napiše in bomo dodali.

Fuj. No, danes smo pokrili vse, kar sem vam želel povedati. Upam, da so bile te informacije koristne. Seznam referenc si lahko preberete sami v zavihku Koristne informacije.

Koristne informacije

Vir: www.habr.com

Dodaj komentar