Sziasztok. Kapcsolatfelvétel Omelnitsky Sergey. Nem is olyan régen egy streamet vezettem a reaktív programozásról, ahol a JavaScript aszinkronjáról beszéltem. Ma ezt az anyagot szeretném összefoglalni.
Mielőtt azonban elkezdenénk a fő anyagot, egy bevezetést kell tennünk. Kezdjük tehát a definíciókkal: mi a verem és a sor?
Kazal egy olyan gyűjtemény, amelynek elemeit utolsó be, elsőként kikerülő LIFO alapon szerezzük be
fordulat egy olyan gyűjtemény, amelynek elemeit a FIFO elve szerint kapjuk meg ("first in, first out").
Oké, folytassuk.
A JavaScript egy egyszálú programozási nyelv. Ez azt jelenti, hogy csak egy végrehajtási szála és egy verem van, ahol a függvények sorba vannak állítva a végrehajtáshoz. Ezért a JavaScript egyszerre csak egy műveletet tud végrehajtani, míg a többi művelet megvárja a sorát a veremben, amíg meg nem hívják.
Hívási lista egy olyan adatstruktúra, amely leegyszerűsítve rögzíti a program azon helyéről szóló információkat, ahol éppen vagyunk. Ha átmegyünk egy függvénybe, akkor a bemenetét a verem tetejére toljuk. Amikor visszatérünk egy függvényből, a legfelső elemet kiemeljük a veremből, és visszakerülünk oda, ahol a függvényt hívtuk. Ez minden, amire a verem képes. És most egy rendkívül érdekes kérdés. Hogyan működik tehát az aszinkron a JavaScriptben?
Valójában a verem mellett a böngészőknek van egy speciális sora az úgynevezett WebAPI-val való együttműködéshez. Az ebben a sorban lévő funkciók csak a verem teljes törlése után hajtódnak végre sorrendben. Csak ezután kerülnek a sorból a verembe végrehajtásra. Ha jelenleg legalább egy elem van a veremben, akkor azokat nem lehet hozzáadni a veremhez. Pontosan emiatt, hogy a függvények időtúllépéssel történő hívása gyakran nem pontos időben, mivel a függvény nem tud a sorból a verembe jutni, amíg az tele van.
Vessünk egy pillantást a következő példára, és menjünk végig rajta lépésről lépésre. Lássuk azt is, mi történik a rendszerben.
1) Egyelőre semmi sem történik. A böngészőkonzol tiszta, a hívási verem üres.
2) Ezután a console.log('Hi') parancs hozzáadódik a hívási veremhez.
3) És teljesül
4) Ezután a console.log('Hi') eltávolításra kerül a hívási veremből.
5) Most lépjen tovább a setTimeout(függvény cb1() {… }) parancsra. Hozzáadjuk a hívásveremhez.
6) A setTimeout(függvény cb1() {… }) parancs végrehajtásra kerül. A böngésző létrehoz egy időzítőt, amely a webes API része. Visszaszámlálást hajt végre.
7) A setTimeout(function cb1() {… }) parancs befejezte munkáját, és eltávolították a hívási veremből.
8) A console.log('Bye') parancs hozzáadódik a hívási veremhez.
9) A console.log('Bye') parancs végrehajtásra kerül.
10) A console.log('Bye') parancs eltávolításra kerül a hívási veremből.
11) Legalább 5000 ms elteltével az időzítő lejár, és a cb1 visszahívást a visszahívási sorba helyezi.
12) Az eseményhurok átveszi a cb1 függvényt a visszahívási sorból, és a hívási verembe helyezi.
13) A cb1 függvény végrehajtásra kerül, és hozzáadja a console.log('cb1') fájlt a hívási veremhez.
14) A console.log('cb1') parancs végrehajtásra kerül.
15) A console.log('cb1') parancs eltávolításra kerül a hívási veremből.
16) A cb1 függvény eltávolítva a hívási veremből.
Nézzünk egy példát a dinamikában:
Nos, megnéztük, hogyan valósítható meg az aszinkron a JavaScriptben. Most beszéljünk röviden az aszinkron kód fejlődéséről.
Az aszinkron kód evolúciója.
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);
})
})
})
})
})
});
Az általunk JavaScriptben ismert aszinkron programozás csak függvényekkel valósítható meg. Mint bármely más változót, átadhatók más függvényeknek. Így születtek a visszahívások. És ez hűvös, szórakoztató és fergeteges, amíg szomorúságba, melankóliába és szomorúságba nem változik. Miért? Igen, ez egyszerű:
A kód összetettségének növekedésével a projekt gyorsan homályos, többször egymásba ágyazott blokkokká válik - „visszahívási pokol”.
A hibakezelés könnyen kimaradhat.
A kifejezéseket nem adhatja vissza return-lel.
Az Ígéret megjelenésével egy kicsit jobb lett a helyzet.
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);
});
Promise láncok jelentek meg, amelyek javították a kód olvashatóságát
Megjelent egy külön módszer a hibák elkapására
Hozzáadtuk a párhuzamos végrehajtás lehetőségét a Promise.all használatával
A beágyazott aszinkront az async/await-tal tudjuk megoldani
De az ígéreteknek megvannak a korlátai. Például egy ígéretet nem lehet lemondani a tamburával való tánc nélkül, és ami a legfontosabb, hogy egy értékkel működik.
Nos, itt simán közeledünk a reaktív programozáshoz. Fáradt? Nos, az a jó, hogy elmehetsz sirályokat főzni, eszedbe jutni, és visszatérsz olvasni. És folytatom.
Reaktív programozás egy programozási paradigma, amely az adatáramlásra és a változások terjedésére összpontosít. Nézzük meg közelebbről, mi is az adatfolyam.
// Получаем ссылку на элемент
const input = ducument.querySelector('input');
const eventsArray = [];
// Пушим каждое событие в массив eventsArray
input.addEventListener('keyup',
event => eventsArray.push(event)
);
Képzeljük el, hogy van egy beviteli mezőnk. Létrehozunk egy tömböt, és a bemeneti esemény minden egyes kulcsához eltároljuk az eseményt a tömbünkben. Ugyanakkor szeretném megjegyezni, hogy a tömbünk idő szerint van rendezve, pl. a későbbi események indexe nagyobb, mint a korábbi események indexe. Egy ilyen tömb egy egyszerűsített adatfolyam-modell, de még nem folyam. Ahhoz, hogy ezt a tömböt biztonságosan folyamnak lehessen nevezni, képesnek kell lennie valamilyen módon tájékoztatni az előfizetőket arról, hogy új adatok érkeztek benne. Így jutunk el az áramlás definíciójához.
folyam egy idő szerint rendezett adattömb, amely jelezheti, hogy az adatok megváltoztak. Most képzelje el, milyen kényelmes lehet olyan kódot írni, amelyben egy művelethez több eseményt kell elindítania a kód különböző részein. Egyszerűen feliratkozunk a streamre, és az értesít minket, ha változások történnek. És az RxJs könyvtár képes erre.
RxJS egy könyvtár az aszinkron és esemény alapú programokkal való munkavégzéshez, megfigyelhető sorozatokat használva. A könyvtár alaptípust biztosít megfigyelhető, többféle kiegészítő típus (Megfigyelő, ütemezők, alanyok) és operátorok az eseményekkel és a gyűjteményekkel való munkavégzéshez (térkép, szűrés, kicsinyítés, minden és hasonlók a JavaScript tömbből).
Ismerjük meg ennek a könyvtárnak az alapfogalmait.
Megfigyelhető, megfigyelő, termelő
A Megfigyelhető az első alaptípus, amelyet megvizsgálunk. Ez az osztály tartalmazza az RxJs megvalósítás fő részét. Megfigyelhető adatfolyamhoz van társítva, amelyre az előfizetési módszerrel lehet előfizetni.
Az Observable a frissítések létrehozására szolgáló segítő mechanizmust, az ún Megfigyelő. Az Observer értékforrását hívják Termelő. Ez lehet tömb, iterátor, web socket, valamilyen esemény stb. Tehát azt mondhatjuk, hogy a megfigyelhető egy vezető a Termelő és a Megfigyelő között.
Az Observable háromféle megfigyelő eseményt kezel:
következő – új adatok
hiba - hiba, ha a sorozat kivétel miatt megszakadt. ez az esemény a sorozat befejezését is magában foglalja.
teljes - egy jelzés a sorozat végéről. Ez azt jelenti, hogy nem lesz több új adat
Lássuk a demót:
Kezdetben az 1, 2, 3 értékeket dolgozzuk fel, majd 1 másodperc múlva. 4-et kapunk és befejezzük a szálunkat.
Hangosan gondolkodni
Aztán rájöttem, hogy érdekesebb mesélni, mint írni róla. 😀
Előfizetés
Amikor feliratkozunk egy adatfolyamra, új osztályt hozunk létre előfizetésamely lehetőséget ad számunkra a leiratkozásra a módszer segítségével leiratkozás. A módszerrel csoportosíthatjuk is az előfizetéseket hozzá. Nos, logikus, hogy a szálakat felbonthatjuk a használatával eltávolítása. A hozzáadási és eltávolítási módszerek más előfizetést fogadnak el bemenetként. Szeretném megjegyezni, hogy amikor leiratkozunk, akkor minden gyermek előfizetésről leiratkozunk, mintha a leiratkozási módot is hívnák. Menj tovább.
A folyamok típusai
FORRÓ
HIDEG
A termelő a megfigyelhetően kívül jön létre
A termelő belül megfigyelhető
Az adatok átadása a megfigyelhető létrehozásakor történik
Az adatok megadása az előfizetéskor történik
Több logika kell a leiratkozáshoz
A szál magától véget ér
Egy-a-többhöz kapcsolatot használ
Egy-egy kapcsolatot használ
Minden előfizetés azonos értékű
Az előfizetések függetlenek
Az adatok elveszhetnek, ha nincs előfizetés
Újra kiadja az összes adatfolyam-értéket egy új előfizetéshez
Hogy egy hasonlatot mondjak, egy forró folyamot képzelek el, mint egy filmet a moziban. Mikor jöttél, attól a pillanattól kezdve elkezdted nézni. Összehasonlítanám a hideg folyamot a hívással. támogatás. Bármelyik hívó meghallgatja az üzenetrögzítő felvételét az elejétől a végéig, de leteheti a telefont a leiratkozással.
Szeretném megjegyezni, hogy vannak úgynevezett meleg áramlások is (rendkívül ritkán és csak külföldi közösségekben találkoztam ezzel a meghatározással) - ez egy hideg áramlásból meleg áramlásba átalakuló áramlás. Felmerül a kérdés - hol kell használni)) Mondok egy példát a gyakorlatból.
Az Angularral dolgozom. Aktívan használja az rxjs-t. Az adatok kiszolgálóhoz való eljuttatásához hideg adatfolyamra számítok, és ezt az adatfolyamot használom a sablonban az asyncPipe segítségével. Ha többször használom ezt a pipet, akkor visszatérve a hideg folyam definíciójához, minden pipe adatot kér a szervertől, ami finoman szólva furcsa. És ha egy hideg patakot melegebbé alakítok át, akkor a kérés egyszer megtörténik.
Általában az áramlások típusának megértése meglehetősen nehéz a kezdőknek, de fontos.
Üzemeltetők
return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
.pipe(
tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
map(({ data }: TradeCompanyList) => data)
);
Az üzemeltetők lehetővé teszik számunkra, hogy bővítsük a streamekkel való munkavégzési képességünket. Segítenek a Megfigyelhetőben előforduló események szabályozásában. Megnézünk párat a legnépszerűbbek közül, az operátorokról további részleteket a hasznos információkban található hivatkozások segítségével találhat meg.
üzemeltetői
Kezdjük a segéd operátorral. Megfigyelhetőt hoz létre egy egyszerű érték alapján.
Operátorok-szűrő
A szűrő operátor, ahogy a neve is sugallja, szűri a stream jelet. Ha az operátor true értéket ad vissza, akkor továbblép.
Üzemeltetők - vegye
take — Az adók számának értékét veszi, amely után a szál véget ér.
Operators-debounceTime
debounceTime - elveti a kibocsátott értékeket, amelyek a kimeneti adatok közötti meghatározott időintervallumba esnek - az időintervallum letelte után kiadja az utolsó értéket.
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)
);
Operators-takeWhile
Értékeket bocsát ki, amíg a takeWhile false értéket nem ad vissza, majd leiratkozik a streamről.
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 )
);
Operátorok-kombájnLegfrissebb
A kombinált combinLatest operátor némileg hasonlít a ígéret.all-hoz. Több adatfolyamot egyesít egybe. Miután minden szál legalább egy emittált, mindegyikből megkapjuk a legújabb értékeket tömbként. Továbbá, a kombinált folyamok bármilyen kibocsátása után új értékeket ad.
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));
Operátorok - zip
Zip – minden adatfolyamból vár egy értéket, és ezek alapján tömböt képez. Ha az érték egyik szálból sem származik, akkor a csoport nem jön létre.
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));
Operátorok - forkJoin
A forkJoin a szálakat is összekapcsolja, de csak akkor ad ki értéket, ha az összes szál elkészült.
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);
Üzemeltetők - térkép
A térképtranszformációs operátor átalakítja az emit értéket egy új értékké.
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)
);
Operátorok – megosztás, koppintás
A koppintási operátor lehetővé teszi mellékhatások végrehajtását, vagyis minden olyan műveletet, amely nem befolyásolja a sorozatot.
A megosztási segédprogram üzemeltetője a hideg folyamot forróvá alakíthatja.
Elkészültünk az operátorokkal. Térjünk át a tárgyra.
Hangosan gondolkodni
Aztán elmentem inni egy kis teát. Elegem van ezekből a példákból 😀
Tárgycsalád
A témacsalád a forró szálak kiváló példája. Ezek az osztályok egyfajta hibridek, amelyek egyszerre működnek megfigyelőként és megfigyelőként. Mivel a téma forró adatfolyam, le kell iratkozni róla. Ha a fő módszerekről beszélünk, akkor ezek a következők:
következő - új adatok átadása az adatfolyamnak
hiba – hiba és menetlezárás
teljes - a szál vége
feliratkozás - feliratkozás egy adatfolyamra
leiratkozás – leiratkozás a streamről
asObservable – átalakuljon megfigyelővé
toPromise – ígéretté alakul át
4 5 féle tantárgy létezik.
Hangosan gondolkodni
4-et mondtam a streamen, de kiderült, hogy hozzáadtak még egyet. Ahogy a mondás tartja, élj és tanulj.
Egyszerű téma new Subject()- a legegyszerűbb típusú tantárgyak. Paraméterek nélkül készült. Átadja azokat az értékeket, amelyek csak az előfizetés után érkeztek.
BehaviorSubject new BehaviorSubject( defaultData<T> ) - szerintem a leggyakoribb tárgytípus. A bemenet az alapértelmezett értéket veszi fel. Mindig elmenti az utolsó szám adatait, amelyeket előfizetéskor továbbít. Ennek az osztálynak van egy hasznos érték metódusa is, amely a folyam aktuális értékét adja vissza.
ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) - Opcionálisan első argumentumnak veheti annak az értékpuffernek a méretét, amelyet önmagában tárol, a második alkalommal pedig, amikor változtatásra van szükségünk.
aszinkron alany new AsyncSubject() - előfizetéskor nem történik semmi, és az értéket csak akkor adjuk vissza, ha kész. Csak az adatfolyam utolsó értéke kerül visszaadásra.
WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - A dokumentáció hallgat róla, és én magam látom először. Aki tudja mit csinál, írjon, mi hozzátesszük.
Fú. Nos, mindent leírtunk, amit ma el akartam mondani. Remélem, ez az információ hasznos volt. Az irodalomjegyzéket önállóan is elolvashatja a Hasznos információk fülön.