Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Pershendetje te gjitheve. Sergej Omelnitsky është në kontakt. Jo shumë kohë më parë unë prita një transmetim mbi programimin reaktiv, ku fola për asinkroninë në JavaScript. Sot dua të mbaj shënime për këtë material.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Por, para se të fillojmë materialin kryesor, duhet të bëjmë një shënim hyrës. Pra, le të fillojmë me përkufizimet: çfarë është një pirg dhe një radhë?

Biftek është një koleksion, elementët e të cilit janë marrë në bazë të LIFO-s me hyrjen e fundit dhe të parën

Radhe është një koleksion, elementët e të cilit janë marrë në bazë të FIFO-së me hyrjen e parë, të parën

Mirë, le të vazhdojmë.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

JavaScript është një gjuhë programimi me një fije të vetme. Kjo do të thotë se ekziston vetëm një thread ekzekutimi dhe një stack në të cilin funksionet janë në radhë për ekzekutim. Prandaj, JavaScript mund të kryejë vetëm një operacion në të njëjtën kohë, ndërsa operacionet e tjera do të presin radhën e tyre në pirg derisa të thirren.

Stafi i thirrjeve është një strukturë të dhënash që, thënë thjesht, regjistron informacione për vendin në programin ku ndodhemi. Nëse kalojmë në një funksion, e shtyjmë hyrjen e tij në krye të pirgut. Kur kthehemi nga një funksion, nxjerrim elementin më të lartë nga grumbulli dhe përfundojmë aty ku kemi thirrur funksionin. Kjo është gjithçka që mund të bëjë pirgu. Dhe tani një pyetje jashtëzakonisht interesante. Atëherë, si funksionon asinkronia në JavasScript?

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Në fakt, përveç stakut, shfletuesit kanë një radhë të veçantë për të punuar me të ashtuquajturin WebAPI. Funksionet në këtë radhë do të ekzekutohen sipas radhës vetëm pasi pirgja të jetë pastruar plotësisht. Vetëm pas kësaj ata shtyhen nga radha në pirg për ekzekutim. Nëse ka të paktën një element në pirg për momentin, atëherë ata nuk mund të shtohen në pirg. Pikërisht për shkak të kësaj, thirrja e funksioneve me kohëzgjatje shpesh nuk është e saktë në kohë, pasi funksioni nuk mund të shkojë nga radha në pirg ndërsa është plot.

Le të shohim shembullin e mëposhtëm dhe të fillojmë me zbatimin e tij hap pas hapi. Le të shohim gjithashtu se çfarë ndodh në sistem.

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

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

1) Asgjë nuk po ndodh ende. Konsola e shfletuesit është e qartë, grupi i thirrjeve është bosh.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

2) Pastaj komanda console.log('Hi') shtohet në grupin e thirrjeve.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

3) Dhe është përmbushur

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

4) Pastaj console.log ('Hi') hiqet nga grupi i thirrjeve.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

5) Tani kaloni te komanda setTimeout(funksioni cb1() {… }). Ai shtohet në grupin e thirrjeve.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

6) Komanda setTimeout(funksioni cb1() {… }) është ekzekutuar. Shfletuesi krijon një kohëmatës që është pjesë e Web API. Do të kryejë një numërim mbrapsht.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

7) Komanda setTimeout(funksioni cb1() {... }) ka përfunduar punën e saj dhe është hequr nga grupi i thirrjeve.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

8) Komanda console.log ('Bye') shtohet në grupin e thirrjeve.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

9) Komanda console.log('Bye') është ekzekutuar.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

10) Komanda console.log ('Bye') hiqet nga grupi i thirrjeve.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

11) Pasi të kenë kaluar të paktën 5000 ms, kohëmatësi përfundon dhe vendos kthimin e thirrjes cb1 në radhën e kthimit të thirrjes.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

12) Cikli i ngjarjes merr funksionin cb1 nga radha e kthimit të thirrjes dhe e vendos atë në pirgun e thirrjeve.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

13) Funksioni cb1 ekzekutohet dhe shton console.log('cb1') në grupin e thirrjeve.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

14) Komanda console.log('cb1') është ekzekutuar.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

15) Komanda console.log('cb1') hiqet nga grupi i thirrjeve.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

16) Funksioni cb1 hiqet nga grupi i thirrjeve.

Le të shohim një shembull në dinamikë:

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Epo, ne shikuam se si zbatohet asinkronia në JavaScript. Tani le të flasim shkurtimisht për evolucionin e kodit asinkron.

Evolucioni i kodit asinkron.

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

Programimi asinkron siç e njohim ne në JavaScript mund të zbatohet vetëm nga funksionet. Ato mund të kalohen si çdo variabël tjetër në funksione të tjera. Kështu lindën thirrjet. Dhe është e lezetshme, argëtuese dhe lozonjare, derisa të kthehet në trishtim, melankoli dhe trishtim. Pse? Është e thjeshtë:

  • Ndërsa kompleksiteti i kodit rritet, projekti kthehet shpejt në blloqe të errëta, të mbivendosura në mënyrë të përsëritur - "ferri i kthimit të thirrjes".
  • Trajtimi i gabimeve mund të jetë i lehtë për t'u humbur.
  • Ju nuk mund të ktheni shprehje me kthim.

Me ardhjen e Premtimit, situata u bë pak më e mirë.

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

  • U shfaqën zinxhirë premtimesh, të cilat përmirësonin lexueshmërinë e kodit
  • Është shfaqur një metodë e veçantë për kapjen e gabimeve
  • U shtua mundësia e ekzekutimit paralel duke përdorur Promise.all
  • Ne mund të zgjidhim asinkroninë e mbivendosur duke përdorur asinkronizim/prit

Por premtimet kanë kufizimet e tyre. Për shembull, një premtim nuk mund të anulohet pa kërcyer me një dajre, dhe më e rëndësishmja është se ai funksionon me një vlerë.

Epo, ne i kemi afruar pa probleme programimit reaktiv. E lodhur? Epo, për fat të mirë, mund të shkoni të bëni një çaj, të mendoni për këtë dhe të ktheheni për të lexuar më shumë. Dhe unë do të vazhdoj.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Programimi reaktiv është një paradigmë programimi e fokusuar në rrjedhat e të dhënave dhe përhapjen e ndryshimeve. Le të hedhim një vështrim më të afërt se çfarë është një rrjedhë e të dhënave.

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

const eventsArray = [];

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

Le të imagjinojmë se kemi një fushë hyrëse. Ne po krijojmë një grup dhe për çdo tastierë të ngjarjes hyrëse do ta ruajmë ngjarjen në grupin tonë. Në të njëjtën kohë, dua të vërej se grupi ynë është i renditur sipas kohës, d.m.th. indeksi i ngjarjeve të mëvonshme është më i madh se indeksi i ngjarjeve të mëparshme. Një grup i tillë është një model i thjeshtuar i një rrjedhe të dhënash, por nuk është ende një rrjedhë. Në mënyrë që ky grup të quhet në mënyrë të sigurt një transmetim, ai duhet të jetë në gjendje të informojë disi pajtimtarët se të dhënat e reja kanë mbërritur në të. Kështu vijmë te përkufizimi i rrjedhës.

Rrjedha e të dhënave

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

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

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Rrjedh është një grup të dhënash të renditura sipas kohës që mund të tregojnë se të dhënat kanë ndryshuar. Tani imagjinoni sa i përshtatshëm bëhet të shkruhet kodi në të cilin një veprim kërkon thirrjen e disa ngjarjeve në pjesë të ndryshme të kodit. Ne thjesht abonohemi në transmetim dhe ai do të na njoftojë kur të ndodhin ndryshime. Dhe biblioteka RxJs mund ta bëjë këtë.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

RxJS është një bibliotekë për të punuar me programe asinkrone dhe të bazuara në ngjarje duke përdorur sekuenca të vëzhgueshme. Biblioteka ofron një lloj bazë E vëzhgueshme, disa lloje ndihmëse (Vëzhguesi, Planifikuesit, Subjektet) dhe operatorët për të punuar me ngjarjet si me koleksionet (hartë, filtro, redukto, çdo dhe të ngjashme nga JavaScript Array).

Le të kuptojmë konceptet bazë të kësaj biblioteke.

I vëzhgueshëm, vëzhgues, prodhues

Observable është lloji i parë bazë që do të shikojmë. Kjo klasë përmban pjesën kryesore të zbatimit të RxJs. Ai shoqërohet me një rrjedhë të vëzhgueshme, e cila mund të abonohet duke përdorur metodën e abonimit.

Observable zbaton një mekanizëm ndihmës për krijimin e përditësimeve, të ashtuquajturat Mbikëqyrës. Burimi i vlerave për Observer quhet Prodhues. Ky mund të jetë një grup, përsëritës, prizë në internet, një lloj ngjarjeje, etj. Pra, mund të themi se e vëzhgueshme është një përcjellës ndërmjet Prodhuesit dhe Vëzhguesit.

Observable trajton tre lloje të ngjarjeve të Observer:

  • tjetër - të dhëna të reja
  • gabim - një gabim nëse sekuenca ka përfunduar për shkak të një përjashtimi. kjo ngjarje nënkupton edhe plotësimin e sekuencës.
  • i plotë - sinjal për përfundimin e sekuencës. Kjo do të thotë se nuk do të ketë më të dhëna të reja.

Le të shohim demonstrimin:

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Në fillim do të përpunojmë vlerat 1, 2, 3 dhe pas 1 sekonde. ne do të marrim 4 dhe do t'i japim fund transmetimit tonë.

Duke menduar me zë të lartë

Dhe atëherë kuptova se të tregoja ishte më interesante sesa të shkruaje për të. 😀

Abonim

Kur abonohemi në një transmetim, krijojmë një klasë të re abonime cila na jep mundësinë të çregjistrohemi duke përdorur metodën unsubscribe. Ne gjithashtu mund të grupojmë abonimet duke përdorur metodën shtoj. Epo, është logjike që ne mund t'i çgrupojmë temat duke përdorur heq. Metodat e shtimit dhe heqjes pranojnë një abonim tjetër si hyrje. Dëshiroj të vërej se kur çregjistrohemi, ne çregjistrohemi nga të gjitha abonimet e fëmijëve sikur të kishin quajtur metodën e çregjistrimit. Shkoni përpara.

Llojet e përrenjve

HOT
FTOH

Prodhuesi është krijuar jashtë të vëzhgueshme
Prodhuesi është krijuar brenda observable

Të dhënat transferohen në kohën kur krijohet e vëzhgueshme
Të dhënat jepen në momentin e abonimit

Duhet logjikë shtesë për çregjistrimin
Fillimi përfundon vetë

Përdor një marrëdhënie një me shumë
Përdor një marrëdhënie një me një

Të gjitha abonimet kanë të njëjtin kuptim
Abonimet janë të pavarura

Të dhënat mund të humbasin nëse nuk keni një abonim
Riboton të gjitha vlerat e transmetimit për një abonim të ri

Për të dhënë një analogji, do të mendoja për një transmetim të nxehtë si një film në një teatër. Në cilën pikë kohore keni mbërritur, që nga ai moment keni filluar të shikoni. Unë do të krahasoja një rrjedhë të ftohtë me një telefonatë në teknologji. mbështetje. Çdo telefonues dëgjon regjistrimin e postës zanore nga fillimi në fund, por ju mund ta mbyllni duke përdorur çabonimin.

Dua të vërej se ekzistojnë edhe të ashtuquajturat rrjedha të ngrohta (këtë përkufizim e kam hasur jashtëzakonisht rrallë dhe vetëm në komunitetet e huaja) - kjo është një rrjedhë që shndërrohet nga një rrjedhë e ftohtë në një të nxehtë. Shtrohet pyetja - ku të përdoret)) Unë do të jap një shembull nga praktika.

Unë jam duke punuar me Angular. Ai përdor në mënyrë aktive rxjs. Për të marrë të dhëna në server, pres një fije të ftohtë dhe e përdor këtë fill në shabllon duke përdorur asyncPipe. Nëse e përdor këtë tub disa herë, atëherë, duke u rikthyer në përkufizimin e një rryme të ftohtë, çdo tub do të kërkojë të dhëna nga serveri, gjë që është e çuditshme për të thënë të paktën. Dhe nëse shndërroj një rrjedhë të ftohtë në një të ngrohtë, atëherë kërkesa do të ndodhë një herë.

Në përgjithësi, të kuptuarit e llojit të flukseve është mjaft e vështirë për fillestarët, por e rëndësishme.

Operatorët

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

Operatorët na ofrojnë mundësinë për të zgjeruar aftësinë tonë për të punuar me transmetime. Ato ndihmojnë në kontrollin e ngjarjeve që ndodhin në Observable. Ne do të shikojmë disa nga më të njohurit, dhe më shumë detaje rreth operatorëve mund të gjenden duke përdorur lidhjet në informacionin e dobishëm.

Operatorët - të

Le të fillojmë me operatorin ndihmës të. Ai krijon një të vëzhgueshme bazuar në një vlerë të thjeshtë.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Operatorët - filtër

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Operatori i filtrit, siç sugjeron emri, filtron sinjalin e transmetimit. Nëse operatori kthen true, ai kalon më tej.

Operatorët - marrin

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

take — Merr vlerën e numrit të emetuesve, pas së cilës filli mbaron.

Operatorët - debounceTime

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

debounceTime - hedh poshtë vlerat e emetuara që bien brenda intervalit kohor të specifikuar midis daljeve - pasi të ketë kaluar intervali kohor, lëshon vlerën e fundit.

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

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Operatorët - takeWhile

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Emeton vlera derisa takeWhile të kthejë false, pas së cilës çabonohet nga thread.

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

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Operatorët - kombinojnë më të fundit

Operatori combinationLatest është disi i ngjashëm me premtimin.all. Ai kombinon fije të shumta në një. Pasi çdo thread bën të paktën një emetim, ne marrim vlerat më të fundit nga secila në formën e një grupi. Më tej, pas çdo emetimi nga rrjedhat e bashkuara, ai do të japë vlera të reja.

Programimi asinkron në 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));

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Operatorët - zip

Zip - Pret një vlerë nga çdo thread dhe formon një grup bazuar në këto vlera. Nëse vlera nuk vjen nga ndonjë thread, atëherë grupi nuk do të formohet.

Programimi asinkron në 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));

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Operatorët - forkJoin

forkJoin gjithashtu bashkon threads, por ai lëshon një vlerë vetëm kur të gjitha threads janë të plota.

Programimi asinkron në 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);

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Operatorët - hartë

Operatori i transformimit të hartës e transformon vlerën e emetuesit në një të re.

Programimi asinkron në 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)
);

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Operatorët – ndajeni, prekni

Operatori i rubinetit ju lejon të bëni efekte anësore, domethënë çdo veprim që nuk ndikon në sekuencën.

Operatori i shërbimeve të përbashkëta mund të kthejë një rrymë të ftohtë në një të nxehtë.

Programimi asinkron në JavaScript (Callback, Promise, RxJs)

Kemi mbaruar me operatorët. Le të kalojmë te Subjekti.

Duke menduar me zë të lartë

Dhe pastaj shkova për të pirë një çaj. Jam lodhur nga këta shembuj 😀

Familja lëndore

Familja lëndore është një shembull kryesor i rrjedhave të nxehta. Këto klasa janë një lloj hibridi që veprojnë në të njëjtën kohë si të vëzhgueshme dhe vëzhguese. Meqenëse tema është një temë e nxehtë, është e nevojshme të çregjistroheni prej saj. Nëse flasim për metodat kryesore, atëherë këto janë:

  • tjetër - transferimi i të dhënave të reja në transmetim
  • gabim - gabim dhe përfundimi i fillit
  • i plotë – plotësimi i fillit
  • abonohuni - abonohuni në një transmetim
  • çregjistrohu - çregjistrohu nga transmetimi
  • as e vëzhgueshme - shndërrohet në vëzhgues
  • toPromise - shndërrohet në premtim

Ekzistojnë 4 5 lloje lëndësh.

Duke menduar me zë të lartë

Ishin 4 persona që flisnin në rrymë, por doli që ata shtuan edhe një. Siç thonë ata, jetoni dhe mësoni.

Subjekt i thjeshtë new Subject()– Lloji më i thjeshtë i lëndëve. Krijuar pa parametra. Transmeton vlerat e marra vetëm pas abonimit.

SjelljaSubjekti new BehaviorSubject( defaultData<T> ) – për mendimin tim, lloji më i zakonshëm i lëndës. Hyrja merr vlerën e paracaktuar. Ruan gjithmonë të dhënat e numrit të fundit, të cilat transmetohen gjatë abonimit. Kjo klasë ka gjithashtu një metodë të vlerës së dobishme, e cila kthen vlerën aktuale të rrymës.

ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) — Hyrja mund të marrë opsionalisht si argument të parë madhësinë e buferit të vlerave që do të ruajë në vetvete dhe si të dytë kohën gjatë së cilës na duhen ndryshime.

AsyncSubject new AsyncSubject() - asgjë nuk ndodh kur abonoheni dhe vlera do të kthehet vetëm kur të përfundojë. Vetëm vlera e fundit e transmetimit do të kthehet.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) — Dokumentacioni hesht për të dhe e shoh për herë të parë. Nëse dikush e di se çfarë bën ai, ju lutemi të shkruani dhe ne do ta shtojmë.

Phew. Epo, ne kemi mbuluar gjithçka që doja t'ju tregoja sot. Shpresoj se ky informacion ishte i dobishëm. Ju mund ta lexoni vetë listën e referencave në skedën e informacionit të dobishëm.

Informata të dobishme

Burimi: www.habr.com

Shto një koment