Hei kaikki. Ota yhteyttä Omelnitsky Sergey. Ei niin kauan sitten isännöin reaktiivista ohjelmointia käsittelevää streamia, jossa puhuin JavaScriptin asynkronisuudesta. Tänään haluan tehdä yhteenvedon tästä materiaalista.
Mutta ennen kuin aloitamme päämateriaalin, meidän on tehtävä johdanto. Joten aloitetaan määritelmistä: mitä ovat pino ja jono?
Pino on kokoelma, jonka elementit haetaan "viimeinen sisään, ensimmäinen ulos" LIFO-periaatteella
käännä on kokoelma, jonka elementit saadaan periaatteen mukaisesti ("first in, first out" FIFO
Okei, jatketaan.
JavaScript on yksisäikeinen ohjelmointikieli. Tämä tarkoittaa, että sillä on vain yksi suoritussäie ja yksi pino, jossa funktiot ovat jonossa suoritettavaksi. Siksi JavaScript voi suorittaa vain yhden toiminnon kerrallaan, kun taas muut toiminnot odottavat vuoroaan pinossa, kunnes niitä kutsutaan.
kutsupino on tietorakenne, joka yksinkertaistetusti tallentaa tietoja ohjelman paikasta, jossa olemme. Jos hyppäämme funktioon, työnnämme sen merkinnän pinon yläosaan. Kun palaamme funktiosta, ponnaamme pinon ylimmän elementin ja päädymme siihen, mistä kutsuimme tämän funktion. Siinä on kaikki, mitä pino voi tehdä. Ja nyt erittäin mielenkiintoinen kysymys. Miten asynkronisuus sitten toimii JavaScriptissä?
Itse asiassa, pinon lisäksi selaimilla on erityinen jono niin sanotun WebAPI:n kanssa työskentelemiseen. Tämän jonon toiminnot suoritetaan järjestyksessä vasta, kun pino on tyhjennetty kokonaan. Vasta sen jälkeen ne sijoitetaan jonosta pinoon suoritettaviksi. Jos pinossa on tällä hetkellä ainakin yksi elementti, ne eivät pääse pinoon. Juuri tästä syystä funktioiden kutsuminen aikakatkaisulla on usein ajallisesti epätarkkoja, koska funktio ei pääse jonosta pinoon sen ollessa täynnä.
Katsotaanpa seuraavaa esimerkkiä ja käydään se läpi vaihe vaiheelta. Katsotaan myös mitä järjestelmässä tapahtuu.
No, tarkastelimme, kuinka asynkronia toteutetaan JavaScriptissä. Puhutaanpa nyt lyhyesti asynkronisen koodin kehityksestä.
Asynkronisen koodin kehitys.
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);
})
})
})
})
})
});
Asynkroninen ohjelmointi sellaisena kuin sen tunnemme JavaScriptissä voidaan tehdä vain funktioilla. Ne voidaan välittää muille funktioille kuten muutkin muuttujat. Näin takaisinsoitot syntyivät. Ja se on siistiä, hauskaa ja kiihkeää, kunnes se muuttuu suruksi, melankoliaksi ja suruksi. Miksi? Kyllä, se on yksinkertainen:
Kun koodin monimutkaisuus kasvaa, projekti muuttuu nopeasti epäselviksi useiksi sisäkkäisiksi lohkoiksi - "takaisinkutsuhelvetiksi".
Virheiden käsittely voidaan helposti jättää huomiotta.
Et voi palauttaa lausekkeita returnilla.
Promisen myötä tilanne on parantunut hieman.
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);
});
Lupausketjuja ilmestyi, mikä paransi koodin luettavuutta
Virheiden sieppaamiseen oli erillinen menetelmä
Rinnakkainen suoritus lisätyn Promise.all:n kanssa
Voimme ratkaista sisäkkäisen asynkronian async/awaitilla
Mutta lupauksella on rajansa. Esimerkiksi lupausta ei voi peruuttaa ilman tamburiinilla tanssimista, ja mikä tärkeintä, se toimii yhdellä arvolla.
No, tässä lähestytään tasaisesti reaktiivista ohjelmointia. Väsynyt? No, hyvä asia on, että voit mennä hauduttamaan lokkeja, aivoriihiä ja palata lukemaan lisää. Ja jatkan.
Reaktiivinen ohjelmointi - tietovirtoihin ja muutosten etenemiseen keskittyvä ohjelmointiparadigma. Katsotaanpa tarkemmin, mitä datavirta on.
// Получаем ссылку на элемент
const input = ducument.querySelector('input');
const eventsArray = [];
// Пушим каждое событие в массив eventsArray
input.addEventListener('keyup',
event => eventsArray.push(event)
);
Kuvitellaan, että meillä on syöttökenttä. Luomme taulukon ja tallennamme tapahtuman jokaiselle syötetapahtuman avaimelle taulukkoomme. Samalla haluan huomauttaa, että taulukkomme on lajiteltu ajan mukaan, ts. myöhempien tapahtumien indeksi on suurempi kuin aikaisempien tapahtumien indeksi. Tällainen taulukko on yksinkertaistettu tietovirtamalli, mutta se ei ole vielä vuo. Jotta tätä ryhmää voitaisiin turvallisesti kutsua virraksi, sen on kyettävä jollakin tavalla ilmoittamaan tilaajille, että siihen on saapunut uutta dataa. Siten tulemme virtauksen määritelmään.
virta on ajan mukaan lajiteltu joukko tietoja, jotka voivat osoittaa, että tiedot ovat muuttuneet. Kuvittele nyt, kuinka kätevää on kirjoittaa koodia, jossa sinun täytyy käynnistää useita tapahtumia koodin eri osissa yhtä toimintoa varten. Tilaamme streamin ja se ilmoittaa meille, kun muutoksia tapahtuu. Ja RxJs-kirjasto voi tehdä tämän.
RxJS on kirjasto asynkronisten ja tapahtumapohjaisten ohjelmien kanssa työskentelyyn havainnoitavien sekvenssien avulla. Kirjasto tarjoaa päätyypin havaittava, useita aputyyppejä (Tarkkailijat, ajoittajat, aiheet) ja operaattorit tapahtumien ja kokoelmien kanssa työskentelyyn (kartta, suodattaa, pienentää, jokainen ja vastaavat JavaScript Arraysta).
Ymmärretään tämän kirjaston peruskäsitteet.
Havaittava, tarkkailija, tuottaja
Observable on ensimmäinen perustyyppi, jota tarkastelemme. Tämä luokka sisältää pääosan RxJs-toteutuksesta. Se liittyy havaittavaan streamiin, joka voidaan tilata tilausmenetelmällä.
Observable toteuttaa päivitysten luomiseen tarkoitetun apumekanismin, ns Tarkkailija. Tarkkailijan arvojen lähdettä kutsutaan Tuottaja. Se voi olla taulukko, iteraattori, verkkoliitäntä, jonkinlainen tapahtuma jne. Voimme siis sanoa, että havaittava on johdin tuottajan ja tarkkailijan välillä.
Observable käsittelee kolmenlaisia Observer-tapahtumia:
seuraava - uudet tiedot
virhe - virhe, jos sekvenssi katkesi poikkeuksen vuoksi. tämä tapahtuma merkitsee myös sekvenssin päättymistä.
valmis - signaali sekvenssin päättymisestä. Tämä tarkoittaa, että uusia tietoja ei enää tule
Katsotaanpa demo:
Aluksi käsittelemme arvot 1, 2, 3 ja 1 sekunnin kuluttua. saamme 4 ja lopetamme ketjumme.
Ajatella ääneen
Ja sitten tajusin, että oli mielenkiintoisempaa kertoa kuin kirjoittaa siitä. 😀
tilaus
Kun tilaamme streamin, luomme uuden luokan tilaus, joka antaa meille mahdollisuuden peruuttaa tilaus menetelmällä peruuttaa. Voimme myös ryhmitellä tilauksia menetelmällä lisätä. No, on loogista, että voimme purkaa säikeitä käyttämällä poistaa. Lisää ja poista -menetelmät hyväksyvät syötteeksi toisen tilauksen. Haluaisin huomauttaa, että kun peruutamme tilauksen, peruutamme kaikki lapsitilaukset ikään kuin he kutsuisivat myös tilauksen peruutusmenetelmää. Mene eteenpäin.
Virtojen tyypit
KUUMA
KYLMÄ
Tuottaja luodaan havaittavan ulkopuolelle
Tuottaja luodaan havaittavan sisällä
Tiedot välitetään, kun havaittava luodaan
Tiedot toimitetaan tilauksen yhteydessä.
Tilauksen peruuttamiseen tarvitaan lisää logiikkaa
Lanka päättyy itsestään
Käyttää yksi-moneen-suhdetta
Käyttää kahdenkeskistä suhdetta
Kaikilla tilauksilla on sama arvo
Tilaukset ovat riippumattomia
Tiedot voivat kadota, jos tilausta ei ole
Julkaisee uudelleen kaikki streamin arvot uutta tilausta varten
Vertailun antamiseksi kuvittelisin kuuman virran kuin elokuvan elokuvateatterissa. Mihin aikaan tulit, siitä hetkestä lähtien aloit katsomaan. Vertaisin kylmää virtaa kutsuun niissä. tuki. Kuka tahansa soittaja kuuntelee puhelinvastaajan nauhoituksen alusta loppuun, mutta voit lopettaa puhelun tilauksen peruuttamistoiminnolla.
Haluaisin huomauttaa, että on myös niin kutsuttuja lämpimiä virtoja (olen tavannut tällaisen määritelmän erittäin harvoin ja vain ulkomaisissa yhteisöissä) - tämä on virta, joka muuttuu kylmästä virrasta kuumaksi. Herää kysymys - missä käyttää)) Annan esimerkin käytännöstä.
Työskentelen Angularin kanssa. Hän käyttää aktiivisesti rxjs:ää. Saadakseni tietoja palvelimelle odotan kylmää streamia ja käytän tätä tietovirtaa mallissa asyncPipe-sovelluksella. Jos käytän tätä putkea useita kertoja, palatakseni kylmävirran määritelmään, jokainen putki pyytää tietoja palvelimelta, mikä on vähintäänkin outoa. Ja jos muutan kylmän virran lämpimäksi, pyyntö tapahtuu kerran.
Yleensä virtaustyypin ymmärtäminen on melko vaikeaa aloittelijoille, mutta tärkeää.
Operaattorit
return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
.pipe(
tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
map(({ data }: TradeCompanyList) => data)
);
Operaattorit tarjoavat meille mahdollisuuden työskennellä streamien kanssa. Ne auttavat hallitsemaan havainnoitavissa olevia tapahtumia. Käsittelemme pari suosituinta, ja lisää tietoa operaattoreista löytyy hyödyllisten tietojen linkeistä.
Operaattorit
Aloitetaan apuoperaattorista. Se luo havaittavan yksinkertaisen arvon perusteella.
Operaattorit-suodatin
Suodatinoperaattori, kuten nimestä voi päätellä, suodattaa stream-signaalin. Jos operaattori palauttaa tosi, se hyppää pidemmälle.
Operaattorit - ota
take - Ottaa arvon emittioiden lukumäärästä, jonka jälkeen stream päättyy.
Operaattorit-debounceTime
debounceTime - hylkää lähetetyt arvot, jotka kuuluvat määritettyyn aikaväliin lähtötietojen välillä - kun aikaväli on kulunut, lähettää viimeisen arvon.
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)
);
Operaattorit-takeWhile
Lähettää arvoja, kunnes takeWhile palauttaa false, lopettaa sitten viestiketjun tilauksen.
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 )
);
Operaattorit-yhdistelmä Viimeisin
Yhdistetty operaattori combinedLatest on jossain määrin samanlainen kuin lupaus.all. Se yhdistää useita virtoja yhdeksi. Kun jokainen säie on tehnyt vähintään yhden emit, saamme kustakin viimeisimmät arvot matriisina. Lisäksi yhdistetyistä virroista saatavan säteilyn jälkeen se antaa uusia arvoja.
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));
Operaattorit-zip
Zip - odottaa arvoa jokaisesta virrasta ja muodostaa taulukon näiden arvojen perusteella. Jos arvo ei tule mistään säikeestä, ryhmää ei muodosteta.
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));
Operaattorit - forkJoin
forkJoin myös yhdistää säikeitä, mutta se lähettää arvon vasta, kun kaikki säikeet ovat valmiit.
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);
Operaattorit-kartta
Karttamuunnosoperaattori muuttaa emit-arvon uudeksi.
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)
);
Operaattorit - jaa, napauta
Napautusoperaattorin avulla voit tehdä sivuvaikutuksia, eli mitä tahansa toimintoja, jotka eivät vaikuta sekvenssiin.
Jakamisoperaattori voi muuttaa kylmän virran kuumaksi virraksi.
Operaattorit ovat valmiit. Siirrytään aiheeseen.
Ajatella ääneen
Ja sitten menin juomaan teetä. Olen kyllästynyt näihin esimerkkeihin 😀
Aiheperhe
Aiheperhe on loistava esimerkki kuumista langoista. Nämä luokat ovat eräänlainen hybridi, joka toimii havainnoivana ja tarkkailijana samanaikaisesti. Koska aihe on kuuma stream, sen tilaus on peruutettava. Jos puhumme tärkeimmistä menetelmistä, nämä ovat:
seuraava - uusien tietojen välittäminen streamiin
virhe - virhe ja säikeen päättäminen
täydellinen - langan loppu
tilaa - tilaa stream
unsubscribe - peruuta ketjun tilaus
asObservable - muuttuu tarkkailijaksi
toPromise - muuttuu lupaukseksi
Varaa 4 5 aihetyyppiä.
Ajatella ääneen
Sanoin 4 streamissa, mutta kävi ilmi, että he lisäsivät yhden. Kuten sanonta kuuluu, elä ja opi.
Yksinkertainen aihe new Subject()- yksinkertaisimmat aiheet. Luotu ilman parametreja. Välittää arvot, jotka tulivat vasta tilauksen jälkeen.
BehaviorSubject new BehaviorSubject( defaultData<T> ) - mielestäni yleisin aihetyyppi. Syöte ottaa oletusarvon. Tallentaa aina viimeisimmän numeron tiedot, jotka välitetään tilauksen yhteydessä. Tällä luokalla on myös hyödyllinen arvomenetelmä, joka palauttaa virran nykyisen arvon.
ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) - Vaihtoehtoisesti se voi ottaa ensimmäisenä argumenttina arvopuskurin koon, jonka se tallentaa itseensä, ja toisen kerran, jolloin tarvitsemme muutoksia.
asynkkisubjekti new AsyncSubject() - Tilauksen aikana ei tapahdu mitään, ja arvo palautetaan vasta, kun se on valmis. Vain virran viimeinen arvo palautetaan.
WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) – Asiasta dokumentaatio on hiljaa ja itse näen sen ensimmäistä kertaa. Kuka tietää mitä tekee, kirjoita, me lisäämme.
Huh huh. No, olemme pohtineet kaikkea, mitä halusin kertoa tänään. Toivottavasti näistä tiedoista oli apua. Voit lukea kirjallisuusluettelon itse Hyödyllisiä tietoja -välilehdeltä.