Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Saluton al ĉiuj. En kontakto Omelnitsky Sergey. Antaŭ ne longe, mi gastigis fluon pri reaktiva programado, kie mi parolis pri malsinkronio en JavaScript. Hodiaŭ mi ŝatus resumi ĉi tiun materialon.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Sed antaŭ ol komenci la ĉefan materialon, ni devas fari enkondukon. Do ni komencu per difinoj: kio estas stack kaj queue?

Pilo estas kolekto kies elementoj estas prenitaj laŭ "lasta enen, unue eksteren" LIFO-bazo

Kolo estas kolekto, kies elementoj estas akiritaj laŭ la principo (“unua eniranta, unue eliranta” FIFO

Bone, ni daŭrigu.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

JavaScript estas unufadena programlingvo. Ĉi tio signifas, ke ĝi havas nur unu fadenon de ekzekuto kaj unu stakon, kie funkcioj estas vicigitaj por ekzekuto. Tial JavaScript povas nur plenumi unu operacion samtempe, dum aliaj operacioj atendos sian vicon sur la stako ĝis ili estos vokataj.

Voka stako estas datumstrukturo kiu, en simplaj terminoj, registras informojn pri la loko en la programo kie ni estas. Se ni saltas en funkcion, ni puŝas ĝian eniron al la supro de la stako. Kiam ni revenas de funkcio, ni elŝprucas la plej supran elementon de la stako kaj finas de kie ni vokis la funkcion. Tion la stako povas fari. Kaj nun tre interesa demando. Kiel do asinkronio funkcias en JavasScript?

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Fakte, krom la stako, retumiloj havas specialan voston por labori kun la tiel nomata WebAPI. Funkcioj de ĉi tiu vico estos ekzekutitaj en ordo nur post kiam la stako estas tute malplenigita. Nur post tio ili estas metitaj de la atendovico sur la stakon por ekzekuto. Se estas almenaŭ unu elemento sur la stako nuntempe, tiam ili ne povas atingi la stakon. Ĝuste pro tio, voki funkciojn per tempotempo ofte estas malpreciza en tempo, ĉar la funkcio ne povas veni de la atendovico al la stako dum ĝi estas plena.

Ni rigardu la sekvan ekzemplon kaj ni trarigardu ĝin paŝon post paŝo. Ni vidu ankaŭ kio okazas en la sistemo.

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

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

1) Ĝis nun nenio okazas. La retumila konzolo estas pura, la voka stako estas malplena.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

2) Tiam la komando console.log('Saluton') estas aldonita al la voka stako.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

3) Kaj ĝi estas plenumita

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

4) Tiam console.log('Saluton') estas forigita de la voka stako.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

5) Nun ni transiru al la komando setTimeout(function cb1() {… }). Ĝi estas aldonita al la voka stako.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

6) La ordono setTimeout(funkcio cb1() {… }) estas ekzekutita. La retumilo kreas tempigilon kiu estas parto de la Reta API. Ĝi faros retronombradon.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

7) La komando setTimeout(function cb1() {… }) finis sian laboron kaj estas forigita el la voka stako.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

8) La komando console.log('Bye') estas aldonita al la voka stako.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

9) La komando console.log('Bye') estas ekzekutita.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

10) La komando console.log('Bye') estas forigita el la voka stako.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

11) Post kiam almenaŭ 5000ms pasis, la tempigilo finiĝas kaj metas la cb1-revokon en la revokvicon.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

12) La okazaĵa buklo prenas funkcion cb1 de la revokatvico kaj puŝas ĝin sur la voka stako.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

13) La cb1 funkcio estas ekzekutita kaj aldonas console.log('cb1') al la voka stako.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

14) La komando console.log('cb1') estas ekzekutita.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

15) La komando console.log('cb1') estas forigita de la voka stako.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

16) Funkcio cb1 estas forigita de la voka stako.

Ni rigardu ekzemplon en dinamiko:

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Nu, ni rigardis kiel malsinkronio estas efektivigita en JavaScript. Nun ni parolu mallonge pri la evoluo de nesinkrona kodo.

La evoluo de nesinkrona kodo.

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

Nesinkrona programado kiel ni konas ĝin en JavaScript povas esti farita nur per funkcioj. Ili povas esti transdonitaj kiel ajna alia variablo al aliaj funkcioj. Tiel naskiĝis revokoj. Kaj ĝi estas mojosa, amuza kaj fervora, ĝis ĝi fariĝas malĝojo, melankolio kaj malĝojo. Kial? Jes, ĝi estas simpla:

  • Dum la komplekseco de la kodo kreskas, la projekto rapide iĝas malklaraj multoblaj nestitaj blokoj - "callback hell".
  • Erartraktado povas esti facile preteratentita.
  • Vi ne povas redoni esprimojn kun return.

Kun la alveno de Promeso, la situacio iom pliboniĝis.

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

  • Aperis promesĉenoj, kiuj plibonigis la legeblecon de la kodo
  • Estis aparta metodo de interkapto de eraroj
  • Paralela ekzekuto kun Promise.all aldonis
  • Ni povas solvi nestitan asinkronion per async/wait

Sed la promeso havas siajn limojn. Ekzemple, promeso, sen dancado per tamburino, ne povas esti nuligita, kaj plej grave, ĝi funkcias kun unu valoro.

Nu, ĉi tie ni glate alproksimiĝas al reaktiva programado. Laca? Nu, la bona afero estas, ke vi povas iri fari kelkajn mevojn, cerbumi kaj reveni por legi pli. Kaj mi daŭrigos.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Reaktiva programado - programa paradigmo fokusita al datumfluoj kaj disvastigo de ŝanĝoj. Ni rigardu pli detale, kio estas datumfluo.

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

const eventsArray = [];

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

Ni imagu, ke ni havas enigkampon. Ni kreas tabelon, kaj por ĉiu klavo de la eniga evento, ni stokos la eventon en nia tabelo. Samtempe, mi ŝatus noti, ke nia tabelo estas ordigita laŭ tempo, t.e. la indekso de postaj eventoj estas pli granda ol la indekso de pli fruaj. Tia tabelo estas simpligita datumfluomodelo, sed ĝi ankoraŭ ne estas fluo. Por ke ĉi tiu aro estu sekure nomita rivereto, ĝi devas iel povi informi abonantojn, ke novaj datumoj alvenis en ĝi. Tiel ni venas al la difino de fluo.

Fluo de datumoj

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

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

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Fluo estas tabelo de datumoj ordigitaj laŭ tempo kiu povas indiki ke la datumoj ŝanĝiĝis. Nun imagu kiel oportune fariĝas skribi kodon, en kiu vi devas ekigi plurajn eventojn en malsamaj partoj de la kodo por unu ago. Ni simple abonas la fluon kaj ĝi diros al ni kiam okazos ŝanĝoj. Kaj la biblioteko RxJs povas fari tion.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

RxJS estas biblioteko por labori kun nesinkronaj kaj okazaĵ-bazitaj programoj uzantaj observeblajn sekvencojn. La biblioteko provizas la ĉefan tipon Observinda, pluraj helptipoj (Observantoj, Planistoj, Subjektoj) kaj funkciigistoj por labori kun eventoj kiel kun kolektoj (mapo, filtri, redukti, ĉiu kaj similaj el JavaScript Array).

Ni komprenu la bazajn konceptojn de ĉi tiu biblioteko.

Observebla, Observanto, Produktanto

Observebla estas la unua baza tipo, kiun ni rigardos. Ĉi tiu klaso enhavas la ĉefan parton de la efektivigo de RxJs. Ĝi estas rilata al observebla rivereto, kiu povas esti abonita uzante la abonmetodon.

Observable efektivigas helpan mekanismon por krei ĝisdatigojn, la tn observanto. La fonto de valoroj por Observanto nomiĝas produktanto. Ĝi povas esti tabelo, iteratoro, interreta ingo, ia evento ktp. Do ni povas diri, ke observebla estas konduktoro inter Produktanto kaj Observanto.

Observebla pritraktas tri specojn de Observer-okazaĵoj:

  • sekva - novaj datumoj
  • eraro - eraro se la sinsekvo finiĝis pro escepto. ĉi tiu okazaĵo ankaŭ implicas la finon de la sekvenco.
  • kompleta - signalo pri la fino de la sinsekvo. Ĉi tio signifas, ke ne estos pli novaj datumoj

Ni vidu demonstraĵon:

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Komence ni prilaboros la valorojn 1, 2, 3, kaj post 1 sek. ni ricevas 4 kaj finas nian fadenon.

Pensante laŭte

Kaj tiam mi konstatis, ke estas pli interese rakonti ol skribi pri tio. 😀

abono

Kiam ni abonas al rivereto, ni kreas novan klason abono, kiu donas al ni la eblon malaboni per la metodo malaboni. Ni ankaŭ povas grupigi abonojn uzante la metodon aldoni. Nu, estas logike, ke ni povas malgrupigi fadenojn uzante forigu. La metodoj aldoni kaj forigi akceptas malsaman abonon kiel enigon. Mi ŝatus rimarki, ke kiam ni malaboniĝas, ni malaboniĝas de ĉiuj infanaj abonoj kvazaŭ ili ankaŭ nomis la malaboni metodo. Antaŭeniri.

Tipoj de riveretoj

HOT
MALVARMA

Produktanto estas kreita ekster la observebla
Produktanto estas kreita ene observebla

Datenoj estas pasigitaj kiam la observebla estas kreita
Datumoj estas provizitaj en la momento de abono.

Bezonas pli da logiko por malaboni
Fadeno finiĝas memstare

Uzas unu-al-multajn rilaton
Uzas unu-al-unu rilaton

Ĉiuj abonoj havas la saman valoron
Abonoj estas sendependaj

Datumoj povas esti perditaj se ne ekzistas abono
Reeldonas ĉiujn fluajn valorojn por nova abono

Por doni analogion, mi imagus varman fluon kiel filmon en kinejo. En kiu momento vi venis, de tiu momento vi komencis rigardi. Mi komparus malvarman fluon kun voko en tiuj. subteno. Ĉiu alvokanto aŭskultas la registrilon de la respondilo de la komenco ĝis la fino, sed vi povas haltigi per malabono.

Mi ŝatus rimarki, ke ekzistas ankaŭ tiel nomataj varmaj riveretoj (tian difinon mi renkontis ege malofte kaj nur en fremdaj komunumoj) - tio estas rivereto, kiu transformiĝas de malvarma rivereto en varman. Estiĝas la demando - kie uzi)) Mi donos ekzemplon el praktiko.

Mi laboras kun Angular. Li aktive uzas rxjs. Por ricevi datumojn al la servilo, mi atendas malvarman fluon kaj mi uzas ĉi tiun fluon en la ŝablono uzante asyncPipe. Se mi plurfoje uzas ĉi tiun tubon, do, revenante al la difino de malvarma fluo, ĉiu tubo petos datumojn de la servilo, kio estas stranga por diri. Kaj se mi konvertas malvarman rivereton al varma, tiam la peto okazos unufoje.

Ĝenerale, kompreni la tipon de fluoj estas sufiĉe malfacila por komencantoj, sed grava.

Telefonistoj

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

Operaciantoj donas al ni ŝancon labori kun riveretoj. Ili helpas kontroli la eventojn fluantajn en la Observebla. Ni konsideros kelkajn el la plej popularaj, kaj pli da informoj pri la telefonistoj troveblas ĉe la ligiloj en utilaj informoj.

Operatoroj-de

Ni komencu per la helpanto operatoro de. Ĝi kreas Observeblan bazitan sur simpla valoro.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Operatoroj-filtrilo

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

La filtrila funkciigisto, kiel la nomo implicas, filtras la fluosignalon. Se la funkciigisto resendas vera, tiam ĝi saltas plu.

Operatoroj - prenu

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

take - Prenas la valoron de la nombro da elsendaĵoj, post kio la rivereto finiĝas.

Operatoroj-debounceTime

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

debounceTime - forĵetas elsenditajn valorojn kiuj falas ene de la specifita tempointervalo inter eligo-datumoj - post kiam la tempointervalo pasis, elsendas la lastan valoron.

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

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Operatoroj-takeWhile

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Elsendas valorojn ĝis takeWhile revenas falsa, tiam malaboniĝas de la rivereto.

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

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Operatoroj-kombiniLastajn

La kombinita operatoro combineLatest estas iom simila al promise.all. Ĝi kombinas plurajn fluojn en unu. Post kiam ĉiu fadeno faris almenaŭ unu elsendon, ni ricevas la plej novajn valorojn de ĉiu kiel tabelo. Plue, post ajna elsendo de la kombinitaj fluoj, ĝi donos novajn valorojn.

Nesinkrona programado en JavaScript (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));

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Operatoroj-zip

Zip - atendas valoron de ĉiu fluo kaj formas tabelon bazitan sur ĉi tiuj valoroj. Se la valoro ne venas de iu fadeno, tiam la grupo ne estos formita.

Nesinkrona programado en JavaScript (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));

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Operatoroj - forkJoin

forkJoin ankaŭ kunligas fadenojn, sed ĝi nur elsendas valoron kiam ĉiuj fadenoj estas kompletaj.

Nesinkrona programado en JavaScript (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);

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Operatoroj-mapo

La mapa transformfunkciigisto transformas la elsendi valoron en novan.

Nesinkrona programado en JavaScript (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)
);

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Funkciistoj - kunhavigi, frapeti

La frapeta operatoro permesas vin fari kromefikojn, tio estas, ajnajn agojn, kiuj ne influas la sekvencon.

La akcia servaĵofunkciigisto povas turni malvarman fluon en varman.

Nesinkrona programado en JavaScript (Callback, Promise, RxJs)

Operaciantoj estas faritaj. Ni transiru al Temo.

Pensante laŭte

Kaj poste mi iris trinki teon. Mi estas laca de ĉi tiuj ekzemploj 😀

Subjekta familio

La subjekta familio estas ĉefa ekzemplo de varmaj fadenoj. Ĉi tiuj klasoj estas speco de hibridoj kiuj funkcias kiel observebla kaj observanto samtempe. Ĉar la temo estas varma fluo, ĝi devas esti malabonita de. Se ni parolas pri la ĉefaj metodoj, tiam ĉi tiuj estas:

  • sekva - pasi novajn datumojn al la rivereto
  • eraro - eraro kaj fadenfino
  • kompleta - fino de la fadeno
  • subscribe - aboni al rivereto
  • unsubscribe - malaboni de la rivereto
  • asObservable - transformiĝi en observanton
  • toPromise - transformiĝas en promeson

Asignu 4 5 specojn de temoj.

Pensante laŭte

Mi diris 4 sur la rivereto, sed montriĝis, ke ili aldonis unu plian. Kiel oni diras, vivu kaj lernu.

Simpla Temo new Subject()- la plej simpla speco de temoj. Kreita sen parametroj. Transdonas la valorojn, kiuj venis nur post la abono.

KondutoSubjekto new BehaviorSubject( defaultData<T> ) - miaopinie la plej ofta speco de subjekto-j. La enigo prenas la defaŭltan valoron. Ĉiam konservas la datumojn de la lasta numero, kiu estas transdonita dum abono. Ĉi tiu klaso ankaŭ havas utilan valormetodon kiu resendas la nunan valoron de la rivereto.

ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) - Laŭvole, ĝi povas preni kiel la unuan argumenton la grandecon de la bufro de valoroj, kiun ĝi stokos en si mem, kaj la duan fojon dum kiu ni bezonas ŝanĝojn.

nesinkrona subjekto new AsyncSubject() - nenio okazas dum abonado, kaj la valoro estos redonita nur kiam kompleta. Nur la lasta valoro de la fluo estos resendita.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - La dokumentaro silentas pri ĝi kaj mi mem vidas ĝin unuafoje. Kiu scias, kion li faras, skribu, ni aldonos.

Huf. Nu, ni pripensis ĉion, kion mi volis rakonti hodiaŭ. Mi esperas, ke ĉi tiu informo estis helpema. Vi povas legi la liston de literaturo memstare en la langeto Utilaj Informoj.

helpema informo

fonto: www.habr.com

Aldoni komenton