Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Sveiki visi. Sergejus Omelnickis susisiekė. Neseniai surengiau srautą apie reaktyvųjį programavimą, kuriame kalbėjau apie „JavaScript“ asinchroniją. Šiandien norėčiau pasižymėti šia medžiaga.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Tačiau prieš pradėdami pagrindinę medžiagą, turime padaryti įvadinę pastabą. Taigi, pradėkime nuo apibrėžimų: kas yra krūva ir eilė?

Stack yra kolekcija, kurios elementai gaunami pagal LIFO principą

Eilė yra kolekcija, kurios elementai gaunami FIFO principu „pirmas į, pirmas išeina“.

Gerai, tęskime.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

JavaScript yra vienos gijos programavimo kalba. Tai reiškia, kad yra tik viena vykdymo gija ir vienas krūvas, kuriame funkcijos yra vykdomos. Todėl „JavaScript“ vienu metu gali atlikti tik vieną operaciją, o kitos operacijos lauks savo eilės, kol bus iškviestos.

Skambučių krūva yra duomenų struktūra, kuri, paprasčiau tariant, įrašo informaciją apie vietą programoje, kurioje mes esame. Jei pereiname prie funkcijos, jos įvedimą nustumiame į krūvos viršų. Kai grįžtame iš funkcijos, iškeliame aukščiausią elementą iš krūvos ir grįžtame ten, kur iškvietėme funkciją. Tai viskas, ką gali padaryti krūva. O dabar labai įdomus klausimas. Kaip tada asinchronija veikia JavaScript?

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Tiesą sakant, be krūvos, naršyklėse yra speciali eilė darbui su vadinamąja WebAPI. Funkcijos šioje eilėje bus vykdomos eilės tvarka tik visiškai išvalius krūvą. Tik po to jie iš eilės perkeliami į krūvą vykdyti. Jei šiuo metu krūvoje yra bent vienas elementas, jų negalima pridėti prie krūvos. Būtent dėl ​​to funkcijų iškvietimas pagal skirtąjį laiką dažnai nėra tikslus, nes funkcija negali patekti iš eilės į krūvą, kol ji yra pilna.

Pažvelkime į šį pavyzdį ir pradėkime nuo nuoseklaus jo įgyvendinimo. Taip pat pažiūrėkime, kas vyksta sistemoje.

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

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

1) Kol kas nieko nevyksta. Naršyklės konsolė aiški, skambučių krūva tuščia.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

2) Tada komanda console.log('Hi') pridedama prie iškvietimų krūvos.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

3) Ir tai išsipildo

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

4) Tada console.log('Hi') pašalinamas iš skambučių krūvos.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

5) Dabar pereikite prie komandos setTimeout (funkcija cb1() {… }). Jis pridedamas prie skambučių krūvos.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

6) Vykdoma komanda setTimeout(function cb1() {… }). Naršyklė sukuria laikmatį, kuris yra žiniatinklio API dalis. Jis atliks atgalinį skaičiavimą.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

7) Komanda setTimeout(function cb1() {... }) baigė savo darbą ir pašalinama iš iškvietimų krūvos.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

8) Komanda console.log('Bye') pridedama prie iškvietimų krūvos.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

9) Vykdoma komanda console.log('Bye').

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

10) Komanda console.log('Bye') pašalinama iš skambučių krūvos.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

11) Praėjus mažiausiai 5000 ms, laikmatis nustoja veikti ir į atgalinio skambučio eilę įdeda atgalinio skambučio cb1.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

12) Įvykio ciklas paima funkciją cb1 iš atgalinio skambinimo eilės ir įdeda ją į skambučių krūvą.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

13) Funkcija cb1 vykdoma ir prideda console.log('cb1') į iškvietimų krūvą.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

14) Vykdoma komanda console.log('cb1').

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

15) Komanda console.log('cb1') pašalinama iš iškvietimų krūvos.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

16) Funkcija cb1 pašalinama iš skambučių krūvos.

Pažvelkime į dinamikos pavyzdį:

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Na, mes pažiūrėjome, kaip asinchronija įgyvendinama „JavaScript“. Dabar trumpai pakalbėkime apie asinchroninio kodo raidą.

Asinchroninio kodo raida.

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

Asinchroninį programavimą, kokį mes žinome „JavaScript“, gali įgyvendinti tik funkcijos. Juos, kaip ir bet kurį kitą kintamąjį, galima perduoti kitoms funkcijoms. Taip gimė atgaliniai skambučiai. Ir tai šaunu, linksma ir žaisminga, kol neperauga į liūdesį, melancholiją ir liūdesį. Kodėl? Tai paprasta:

  • Didėjant kodo sudėtingumui, projektas greitai virsta neaiškiais, pakartotinai įdėtais blokais - „atskambinimo pragaru“.
  • Klaidų apdorojimas gali būti lengvai nepastebimas.
  • Negalite grąžinti išraiškų su return.

Atsiradus pažadui padėtis šiek tiek pagerėjo.

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

  • Atsirado pažadų grandinės, kurios pagerino kodo skaitomumą
  • Atsirado atskiras klaidų gaudymo būdas
  • Pridėta lygiagretaus vykdymo galimybė naudojant Promise.all
  • Įdėtą asinchroniją galime išspręsti naudodami async/await

Tačiau pažadai turi savo ribas. Pavyzdžiui, pažado negalima atšaukti nešokant su tamburinu, o svarbiausia, kad jis veiktų su viena vertybe.

Na, mes sklandžiai priartėjome prie reaktyvaus programavimo. Pavargęs? Na, laimei, galite eiti išsivirti arbatos, pagalvoti ir grįžti paskaityti daugiau. Ir aš tęsiu.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Reaktyvusis programavimas yra programavimo paradigma, orientuota į duomenų srautus ir pokyčių sklaidą. Pažiūrėkime atidžiau, kas yra duomenų srautas.

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

const eventsArray = [];

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

Įsivaizduokime, kad turime įvesties lauką. Mes kuriame masyvą ir kiekvienam įvesties įvykio raktui išsaugosime įvykį savo masyve. Tuo pačiu noriu pastebėti, kad mūsų masyvas yra surūšiuotas pagal laiką, t.y. vėlesnių įvykių indeksas yra didesnis nei ankstesnių. Toks masyvas yra supaprastintas duomenų srauto modelis, tačiau tai dar nėra srautas. Kad šis masyvas būtų saugiai vadinamas srautu, jis turi kažkaip informuoti abonentus, kad į jį atkeliavo nauji duomenys. Taigi mes priėjome prie srauto apibrėžimo.

Duomenų srautas

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

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

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Srautas yra duomenų masyvas, surūšiuotas pagal laiką, kuris gali rodyti, kad duomenys pasikeitė. Dabar įsivaizduokite, kaip patogu rašyti kodą, kuriame vienas veiksmas reikalauja iškviesti kelis įvykius skirtingose ​​kodo dalyse. Mes tiesiog užsiprenumeruojame srautą ir jis mums praneš, kai įvyks pakeitimai. Ir RxJs biblioteka gali tai padaryti.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

RxJS yra biblioteka, skirta darbui su asinchroninėmis ir įvykiais pagrįstomis programomis naudojant stebimas sekas. Biblioteka pateikia pagrindinį tipą Stebimas, keletas pagalbinių tipų (Stebėtojas, planuotojai, subjektai) ir operatorius darbui su renginiais kaip su kolekcijomis (žemėlapis, filtras, sumažinimas, kas ir panašius iš JavaScript Array).

Supraskime pagrindines šios bibliotekos sąvokas.

Stebimas, Stebėtojas, Gamintojas

Pastebimas yra pirmasis pagrindinis tipas, į kurį žiūrėsime. Šioje klasėje yra pagrindinė RxJs įgyvendinimo dalis. Jis susietas su stebimu srautu, kurį galima užsiprenumeruoti naudojant prenumeratos metodą.

Observable įdiegia pagalbinį atnaujinimų kūrimo mechanizmą, vadinamąjį Stebėtojas. Stebėtojo vertybių šaltinis vadinamas Gamintojas. Tai gali būti masyvas, iteratorius, žiniatinklio lizdas, koks nors įvykis ir pan. Taigi galime sakyti, kad stebimasis yra laidininkas tarp gamintojo ir stebėtojo.

Observable tvarko trijų tipų stebėtojo įvykius:

  • kitas – nauji duomenys
  • klaida – klaida, jei seka baigėsi dėl išimties. šis įvykis taip pat reiškia sekos užbaigimą.
  • baigtas – signalas apie sekos užbaigimą. Tai reiškia, kad naujų duomenų nebebus.

Pažiūrėkime demonstracinę versiją:

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Pradžioje apdorosime reikšmes 1, 2, 3, o po 1 sekundės. gausime 4 ir baigsime savo srautą.

Mąstymas garsiai

Ir tada supratau, kad pasakoti tai įdomiau nei rašyti. 😀

prenumerata

Kai užsiprenumeruojame srautą, sukuriame naują klasę prenumeratakuri suteikia mums galimybę atsisakyti prenumeratos naudojant metodą Atsisakyti. Taip pat galime sugrupuoti prenumeratas naudodami metodą add. Na, logiška, kad galime išgrupuoti gijas naudodami remove. Pridėti ir pašalinti metodai priima kitą prenumeratą kaip įvestį. Noriu pastebėti, kad kai atsisakome prenumeratos, atsisakome visų vaikų prenumeratų, tarsi jie būtų iškvietę prenumeratos atsisakymo metodą. Pirmyn.

Upelių tipai

KARŠTAS
ŠALTA

Gamintojas sukurtas už stebimo ribų
Gamintojas sukurtas stebimas viduje

Duomenys perduodami stebimo objekto sukūrimo metu
Duomenys pateikiami prenumeratos metu

Norint atsisakyti prenumeratos, reikia papildomos logikos
Siūlas baigiasi savaime

Naudoja ryšį vienas su daugeliu
Naudoja ryšį vienas su vienu

Visos prenumeratos turi tą pačią reikšmę
Prenumeratos yra nepriklausomos

Duomenys gali būti prarasti, jei neturite prenumeratos
Iš naujo išduoda visas srauto vertes naujai prenumeratai

Kad būtų galima pateikti analogiją, aš galvočiau apie karštą srautą kaip apie filmą teatre. Kuriuo laiko momentu atvykote, nuo to momento pradėjote žiūrėti. Šaltą srautą palyginčiau su techninės pagalbos iškvietimu. parama. Bet kuris skambinantis asmuo klausosi balso pašto įrašo nuo pradžios iki pabaigos, bet galite nutraukti ragelį naudodami prenumeratos atsisakymą.

Noriu pastebėti, kad yra ir vadinamųjų šiltų srautų (su tokiu apibrėžimu susiduriu itin retai ir tik užsienio bendruomenėse) – tai srautas, kuris iš šalto srauto transformuojasi į karštą. Kyla klausimas - kur naudoti)) Pateiksiu pavyzdį iš praktikos.

Dirbu su Angular. Jis aktyviai naudoja rxjs. Norėdamas gauti duomenis į serverį, tikiuosi šaltos gijos ir naudoju šią giją šablone naudodamas asyncPipe. Jei aš naudoju šį vamzdį keletą kartų, tada, grįžtant prie šalto srauto apibrėžimo, kiekvienas vamzdis paprašys duomenų iš serverio, o tai švelniai tariant, keista. O jei šaltą srovę konvertuosiu į šiltą, tai prašymas įvyks vieną kartą.

Apskritai pradedantiesiems gana sunku suprasti srautų tipą, tačiau svarbu.

Operatoriai

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

Operatoriai suteikia mums galimybę išplėsti savo galimybes dirbti su srautais. Jie padeda kontroliuoti įvykius, vykstančius Stebimajame. Apžvelgsime porą populiariausių, o daugiau informacijos apie operatorius rasite naudingoje informacijoje esančiomis nuorodomis.

Operatoriai – iš

Pradėkime nuo pagalbinio operatoriaus. Jis sukuria Stebimąjį, pagrįstą paprasta verte.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Operatoriai – filtras

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Filtro operatorius, kaip rodo pavadinimas, filtruoja srauto signalą. Jei operatorius grąžina true, jis praleidžia toliau.

Operatoriai – imk

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

imti — paima emiterių skaičiaus reikšmę, po kurios sriegis baigiasi.

Operatoriai – debounceTime

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

debounceTime - atmeta skleidžiamas vertes, kurios patenka į nurodytą laiko intervalą tarp išėjimų - praėjus laiko intervalui, išleidžia paskutinę reikšmę.

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

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Operatoriai – takeWhile

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Išleidžia reikšmes, kol takeWhile grąžina false, po to atšaukia gijos prenumeratą.

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

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Operatoriai - sujungti Naujausi

CombinLatest operatorius yra šiek tiek panašus į pažadus.all. Jis sujungia kelis siūlus į vieną. Po to, kai kiekviena gija sukuria bent vieną emisiją, gauname naujausias kiekvieno iš jų vertes masyvo pavidalu. Be to, po bet kokios emisijos iš sujungtų srautų jis suteiks naujų verčių.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, 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));

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Operatoriai – užtrauktukas

Zip – laukia reikšmės iš kiekvienos gijos ir pagal šias reikšmes sudaro masyvą. Jei reikšmė nėra kilusi iš jokios gijos, grupė nebus suformuota.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, 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));

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Operatoriai – forkJoin

forkJoin taip pat sujungia gijas, bet išleidžia reikšmę tik tada, kai visos gijos yra baigtos.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, 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);

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Operatoriai – žemėlapis

Žemėlapio transformacijos operatorius transformuoja emiterio vertę į naują.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, 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)
);

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Operatoriai – bendrinkite, bakstelėkite

Bakstelėjimo operatorius leidžia atlikti šalutinį poveikį, tai yra, bet kokius veiksmus, kurie neturi įtakos sekai.

Dalijimosi paslaugų operatorius gali paversti šaltą srautą karštu.

Asinchroninis programavimas „JavaScript“ (atskambinimas, pažadas, RxJs)

Su operatoriais baigėme. Pereikime prie temos.

Mąstymas garsiai

Ir tada nuėjau išgerti arbatos. Pavargau nuo šitų pavyzdžių 😀

Dalyko šeima

Dalykų šeima yra puikus karštų srautų pavyzdys. Šios klasės yra tam tikras hibridas, kuris vienu metu veikia kaip stebimasis ir stebėtojas. Kadangi tema yra karšta tema, būtina jos atsisakyti. Jei mes kalbame apie pagrindinius metodus, tai yra šie:

  • kitas – naujų duomenų perkėlimas į srautą
  • klaida – klaida ir gijos pabaiga
  • užbaigti – gijos užbaigimas
  • prenumeruoti – užsiprenumeruoti srautą
  • atšaukti prenumeratą – atsisakyti srauto prenumeratos
  • as Observable – transformuotis į stebėtoją
  • toPromise – virsta pažadu

Yra 4 5 dalykų tipai.

Mąstymas garsiai

Sraute kalbėjosi 4 žmonės, bet paaiškėjo, kad jie pridėjo dar vieną. Kaip sakoma, gyvenk ir mokykis.

Paprasta tema new Subject()– paprasčiausias dalykų tipas. Sukurta be parametrų. Perduoda vertes, gautas tik po prenumeratos.

BehaviorSubject new BehaviorSubject( defaultData<T> ) – mano nuomone, labiausiai paplitęs dalyko tipas. Įvestis įgauna numatytąją reikšmę. Visada išsaugo paskutinio numerio duomenis, kurie perduodami prenumeruojant. Ši klasė taip pat turi naudingą vertės metodą, kuris grąžina dabartinę srauto vertę.

ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) — Pirmuoju argumentu įvestis gali būti pasirinktas reikšmių buferio dydis, kurį jis išsaugos, o antrasis – laikas, per kurį mums reikia pakeitimų.

AsyncSubject new AsyncSubject() — prenumeruojant nieko neįvyksta, o vertė bus grąžinta tik baigus. Bus grąžinta tik paskutinė srauto vertė.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) – Dokumentuose apie jį nekalbama ir aš jį matau pirmą kartą. Jei kas žino, ką jis veikia, parašykite ir mes pridėsime.

pfu. Na, mes aptarėme viską, ką šiandien norėjau jums pasakyti. Tikiuosi, kad ši informacija buvo naudinga. Literatūros sąrašą galite perskaityti patys naudingos informacijos skirtuke.

naudinga informacija

Šaltinis: www.habr.com

Добавить комментарий