Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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?

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

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

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

1) Egyelőre semmi sem történik. A böngészőkonzol tiszta, a hívási verem üres.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

2) Ezután a console.log('Hi') parancs hozzáadódik a hívási veremhez.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

3) És teljesül

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

4) Ezután a console.log('Hi') eltávolításra kerül a hívási veremből.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

5) Most lépjen tovább a setTimeout(függvény cb1() {… }) parancsra. Hozzáadjuk a hívásveremhez.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

7) A setTimeout(function cb1() {… }) parancs befejezte munkáját, és eltávolították a hívási veremből.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

8) A console.log('Bye') parancs hozzáadódik a hívási veremhez.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

9) A console.log('Bye') parancs végrehajtásra kerül.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

10) A console.log('Bye') parancs eltávolításra kerül a hívási veremből.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

12) Az eseményhurok átveszi a cb1 függvényt a visszahívási sorból, és a hívási verembe helyezi.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

14) A console.log('cb1') parancs végrehajtásra kerül.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

15) A console.log('cb1') parancs eltávolításra kerül a hívási veremből.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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:

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

Adatfolyam

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

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

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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:

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

Operátorok-szűrő

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

take — Az adók számának értékét veszi, amely után a szál véget ér.

Operators-debounceTime

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

Operators-takeWhile

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, 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));

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, 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));

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, 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);

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

Üzemeltetők - térkép

A térképtranszformációs operátor átalakítja az emit értéket egy új értékké.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, 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)
);

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

Aszinkron programozás JavaScriptben (visszahívás, ígéret, RxJs)

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.

hasznos információkat

Forrás: will.com

Hozzászólás