Përshëndetje të gjithëve. Këtu është Sergey Omel'nytskyi. Kohët e fundit kam mbajtur një transmision në lidhje me programimin reaktiv, ku tregova për asinkronicitetin në JavaScript. Sot do të doja të përmbledh këtë materiale.

Por para se të fillojmë me materialin kryesor, na nevojitet një hyrje. Pra, le të fillojmë me përkufizimet: çfarë është steku dhe queues?
Steku është një koleksion, elementët e të cilit merren sipas parimit "i fundit i ardhur, i pari i dalë" LIFO
Queue është një koleksion, elementët e të cilit merren sipas parimit "i pari i ardhur, i pari i dalë" FIFO
Mirë, vazhdojmë.

JavaScript është një gjuhë programimi me një thread. Kjo do të thotë se ka vetëm një thread ekzekutimi dhe një stek, në të cilin vendosen funksionet për ekzekutim. Prandaj, në një moment të vetëm, JavaScript mund të ekzekutojë vetëm një operacion, ndërsa operacionet e tjera do të presin në radhë në stek derisa të thirren.
Steku i thirrjeve është një strukturë të dhënash që, në mënyrë të thjeshtë, regjistron informacionin mbi vendin në program ku ndodhemi. Kur kalojmë në një funksion, vendosim një regjistrim të tij në krye të stekut. Kur kthehemi nga funksioni, heqim elementin më të sipërm nga steku dhe kthehemi aty ku e kemi thirrur këtë funksion. Këto janë të gjitha aftësitë e stekut. Tani, pyetja shumë interesante. Si funksionon asinkroniciteti në JavaScript?

Në të vërtetë, përveç stekut, në shfletues ekziston një radhë e veçantë për të punuar me atë që quhet WebAPI. Funksionet në këtë radhë do të ekzekutohen me rend vetëm pasi steku të pastruar plotësisht. Vetëm pas kësaj ato vendosen nga radhë në stek për ekzekutim. Nëse në stek në këtë moment ndodhet të paktën një element, ato nuk mund të hyjnë në stek. Pikërisht për këtë arsye thirrja e funksioneve me vonesë shpesh është e pasaktë në kohë, pasi funksioni nuk mund të hyjë nga radhë në stek derisa ai të jetë plot.
Le të shqyrtojmë shembullin e mëposhtëm dhe të merremi me "ekzekutimin" e tij hap pas hapi. Po ashtu, le të shohim se çfarë ndodh me këtë në sistem.
console.log('Hi);
setTimeout(function cb1() {
console.log('cb1');
}, 5000);
console.log('Bye');
1) Deri tani nuk ndodh asgjë. Konsola e shfletuesit është e pastër, steku i thirrjeve është bosh.

2) Më pas komanda console.log('Hi') shtohet në stekun e thirrjeve.

3) Dhe ajo ekzekutohet

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

5) Tani kalojmë te komanda setTimeout(function cb1() {⊠}). Ajo shtohet në stoku i thirrjeve.

6) Komanda setTimeout(function cb1() {⊠}) ekzekutohet. Shfletuesi krijon një timer, që është pjesë e Web API. Ai do të realizojë një numërim mbrapsht.

7) Komanda setTimeout(function cb1() {⊠}) përfundon punën dhe hiqet nga stoku i thirrjeve.

8) Komanda console.log('Bye') shtohet në stoku i thirrjeve.

9) Komanda console.log('Bye') ekzekutohet.

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

11) Pasi të kalojnë të paktën 5000 ms., timer-i përfundon punën dhe vendos callback-un cb1 në radhën e callbacks.

12) Cikli i ngjarjeve merr funksionin cb1 nga radhja e callbacks dhe e vendos atë në stoku i thirrjeve.

13) Funksioni cb1 ekzekutohet dhe shton console.log('cb1') në stoku i thirrjeve.

14) Komanda console.log('cb1') ekzekutohet.

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

16) Funksioni cb1 hiqet nga stoku i thirrjeve.
Shikojmë një shembull në dinamikë:

Ja se si e kemi shqyrtuar se si asinkronizimi është realizuar në JavaScript. Tani le të flasim shkurt 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 JavaScript, mund të realizohet vetëm me funksione. Ato mund të kalohen si çdo variabël tjetër te funksione të tjera. Kështu lindi callback-u. Dhe është argëtues derisa të kthehet në trishtim dhe mërzi. Pse? Sepse është e thjeshtë:
- Me rritjen e kompleksitetit të kodit, projekti shpejt kthehet në blloqe shumë të thelluara, një situatë që quhet "helli i callbacks".
- Menaxhimi i gabimeve mund të humbasë lehtësisht.
- Nuk mund të kthehen shprehje me return.
Me shfaqjen e Promise, 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);
});- Lindi zinxhirët e promesave, që përmirësuan lexueshmërinë e kodit.
- Lindi një metodë e veçantë për kapjen e gabimeve.
- Ka ardhur mundësia e ekzekutimit paralel me ndihmën e Promise.all
- Ne mund ta zgjidhim asinkroninë e thellë me ndihmën e async/await
Por premtimi ka kufizimet e veta. Për shembull, një premtim nuk mund të Anulohet pa ndihmën e ndonjë triku, dhe ajo që ka rëndësi - punon me një vlerë.
Ja, tani jemi afruar me programimin reaktiv. Të lodhur? Fatmirësisht, mund të shkoni të bëni një çaj, të reflektoni dhe të ktheheni për të lexuar më tej. Unë do të vazhdoj.

Programimi reaktivâ Ă«shtĂ« njĂ« paradigmĂ« programimi qĂ« fokusohet nĂ« rrjedhat e tĂ« dhĂ«nave dhe pĂ«rhapjen e ndryshimeve. Le tĂ« shqyrtojmĂ« mĂ« nĂ« detaje se çfarĂ« Ă«shtĂ« njĂ« rrjedhĂ« tĂ« dhĂ«nash.
// ĐĐŸĐ»ŃŃĐ°Đ”ĐŒ ŃŃŃĐ»ĐșŃ ĐœĐ° ŃĐ»Đ”ĐŒĐ”ĐœŃ
const input = ducument.querySelector('input');
const eventsArray = [];
// ĐŃŃĐžĐŒ ĐșĐ°Đ¶ĐŽĐŸĐ” ŃĐŸĐ±ŃŃОД ĐČ ĐŒĐ°ŃŃĐžĐČ eventsArray
input.addEventListener('keyup',
event => eventsArray.push(event)
);Imagjinoni se kemi një fushë hyrjeje. Ne krijojmë një array, dhe për çdo ngjarje keyup të inputit, do të ruajmë ngjarjen në array-in tonë. Në këtë rast, duhet të theksojmë se array-i ynë është i renditur sipas kohës, pra indeksi i ngjarjeve më të vonshme është më i madh se indeksi i ngjarjeve më të hershme. Ky array paraqet një model të thjeshtuar të rrjedhës së të dhënave, por akoma nuk është një rrjedhë. Për ta quajtur këtë array me të drejtë një rrjedhë, ai duhet të jetë në gjendje të komunikojë ndonjë mënyrë me abonentët se janë pranuar të dhëna të reja. Kështu kemi arritur në definimin e rrjedhës.
Rrjedha e të dhënave
const { interval1 } = Rx;
const { take } = RxOperators;
interval(1000).pipe(
take(4)
)
Rrjedhaâ Ă«shtĂ« njĂ« array i tĂ« dhĂ«nave tĂ« renditura sipas kohĂ«s, i cili mund tĂ« komunikojĂ« se tĂ« dhĂ«nat janĂ« ndryshuar. Tani imagjinoni se sa e lehtĂ« bĂ«het tĂ« shkruani kod, nĂ« tĂ« cilin pĂ«r njĂ« veprim kĂ«rkohen tĂ« thirren disa ngjarje nĂ« pjesĂ« tĂ« ndryshme tĂ« kodit. Ne thjesht abonohemi nĂ« rrjedhĂ« dhe ajo na informon kur ndodhin ndryshimet. Kjo Ă«shtĂ« ajo qĂ« bĂ«n biblioteka RxJs.

RxJS â Ă«shtĂ« njĂ« bibliotekĂ« pĂ«r tĂ« punuar me programe asinkrone dhe tĂ« bazuara nĂ« ngjarje duke pĂ«rdorur sekuenca tĂ« vĂ«zhguara. Biblioteka ofron tipin kryesor Observable, disa tipe ndihmĂ«se (Observer, Schedulers, Subjects) dhe operatorĂ« pĂ«r tĂ« punuar me ngjarje si me koleksione (map, filter, reduce, every dhe tĂ« ngjashme nga JavaScript Array).
Le të shqyrtojmë konceptet themelore të kësaj biblioteke.
Observable, Observer, Producer
Observable â tipi i parĂ« bazik qĂ« do tĂ« shqyrtojmĂ«. Ky klas pĂ«rmban shumicĂ«n e implementimit tĂ« RxJs. Ai Ă«shtĂ« i lidhur me njĂ« rrjedhĂ« tĂ« vĂ«zhguar, nĂ« tĂ« cilĂ«n mund tĂ« abonohemi duke pĂ«rdorur metodĂ«n subscribe.
Në Observable realizohet një mekanizëm ndihmës për krijimin e azhurnimeve, të ashtuquajturin Observer. Burimi i vlerave për Observer quhet Producer. Kjo mund të jetë një array, një iterator, një web socket, një ndodhi etj. Pra, mund të themi se observable është një ndërmjetës midis Producer dhe Observer.
Observable trajton tre lloje të ngjarjeve të Observer:
- next â tĂ« dhĂ«na tĂ« reja
- error â njĂ« gabim, nĂ«se sekuenca pĂ«rfundoi pĂ«r shkak tĂ« njĂ« situate tĂ« jashtme. Ky ngjarje gjithashtu supozon pĂ«rfundimin e sekuencĂ«s.
- complete â njĂ« 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 një demo:

Në fillim do të trajtojmë vlerat 1, 2, 3, dhe pas 1 sekonde, do të marrim 4 dhe do të përfundojmë rrjedhën tonë.
Mendime në zë
Dhe kĂ«tu e kuptova, qĂ« tĂ« flasĂ«sh ishte mĂ« interesante se sa tĂ« shkruash pĂ«r kĂ«tĂ«. đ
Subscription
Kur abonohesh në një rrjedhë, krijojmë një klasë të re subscription, e cila na jep mundësinë për të anuluar abonimin duke përdorur metodën unsubscribe. Po ashtu, mund të grumbullojmë abonimet duke përdorur metodën add. Dhe logjikisht, mund të ndajmë rrjedhat duke përdorur remove. Metodat add dhe remove pranojnë një abonim tjetër si hyrje. Dëshiroj të theksoj se kur bëjmë anullimin, ne anulojmë të gjitha abonimet fëmijë siç do të kishim thirrur metodën unsubscribe për to. Le të vazhdojmë.
Llojet e rrjedhave
| HOT | COLD |
|---|---|
| Producer krijohet jashtë observable | Producer krijohet brenda observable |
| Të dhënat transmetohen në momentin e krijimit të observable | Të dhënat komunikohen në momentin e abonimit |
| Kërkohet logjikë shtesë për anulimin | Rrjedha përfundon vetë |
| Përdor lidhje një-në-marrë | Përdor lidhje një-në-një |
| Të gjitha abonimet kanë një vlerë të vetme | Abonimet janë të pavarura |
| Të dhënat mund të humben nëse nuk ka abonim | Rikthe gjithë vlerat e rrjedhës për një abonim të ri |
NĂ«se duhet tĂ« bĂ«j njĂ« analogji, do ta paraqisja rrjedhĂ«n e ngrohtĂ« si njĂ« film nĂ« kinematĂ«. NĂ« cilin moment ke ardhur, qĂ« nga ai moment e ke filluar shikimin. Rrjedha e ftohtĂ« do ta krahasoja me njĂ« telefonatĂ« nĂ« mbĂ«shtetje teknike. Ădo person qĂ« telefonon dĂ«gjon regjistrimin e automatikĂ«ve nga fillimi nĂ« fund, por ti mund tĂ« heqĂ«sh telefonin duke pĂ«rdorur unsubscribe.
Do tĂ« doja tĂ« theksoja se ekzistojnĂ« edhe ato qĂ« quhen rrjedha tĂ« ngrohta (kjo pĂ«rkufizim e kam takuar shumĂ« rrallĂ« dhe vetĂ«m nĂ« komunitetet e huaja) â kjo Ă«shtĂ« njĂ« rrjedhĂ« qĂ« transformohet nga njĂ« rrjedhĂ« e ftohtĂ« nĂ« njĂ« tĂ« ngrohtĂ«. Lind pyetja â ku tĂ« pĂ«rdorim? )) Do tĂ« jap njĂ« shembull nga praktika.
Unë punoj me Angular. Ai aktivisht përdor RxJS. Për të marrë të dhënat në server, unë pres një rrjedhë të ftohtë dhe e përdor këtë rrjedhë në shabllon me ndihmën e asyncPipe. Nëse e përdor këtë pipe disa herë, duke u rikthyer te përkufizimi i rrjedhës së ftohtë, çdo pipe do të kërkojë të dhëna nga serveri, që është paksa e çuditshme. Nëse e transformoj rrjedhën e ftohtë në të ngrohtë, atëherë kërkesa do të ndodhi një herë.
Në fakt, kuptimi i llojeve të rrjedhave është mjaft i komplikuar për fillestarët, por është i rëndësishëm.
Operators
return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
.pipe(
tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
map(({ data }: TradeCompanyList) => data)
);Ofruesit e operatorëve zgjeruan mundësinë e punës me rrjedhat. Ata ndihmojnë në kontrollimin e ngjarjeve që ndodhin në Observable. Ne do të shqyrtojmë disa nga më të njohurit, ndërsa për më shumë informacion mbi operatorët mund të konsultoheni me lidhjet në informacionin e dobishëm.
Operators â of
TĂ« fillojmĂ« me operatorin ndihmĂ«s âofâ. Ai krijon njĂ« Observable mbi njĂ« vlerĂ« tĂ« thjeshtĂ«.

Operators â filter

Operatori i filtrimit âfilterâ, siç mund ta kuptoni nga emri, filtroh sinjalin e rrjedhĂ«s. NĂ«se operatori kthen tĂ« vĂ«rtetĂ«n, atĂ«herĂ« e kalon mĂ« tej.
Operators â take

âtakeâ â Merr vlerĂ«n e numrit tĂ« emitimeve, pas tĂ« cilit pĂ«rfundon rrjedhĂ«n.
Operators â debounceTime

âdebounceTimeâ â hedh vlerat e emituara qĂ« bien brenda njĂ« intervali tĂ« caktuar kohor midis daljeve â pas kalimit tĂ« intervalit tĂ« caktuar emiton vlerĂ«n e fundit.
const { Observable } = Rx;
const { debounceTime, take } = RxOperators;
Observable.create((observer) => {
let i = 1;
observer.next(i++);
// Emiton vlerën çdo 1000ms
setInterval(() => {
observer.next(i++)
}, 1000);
// Emiton vlerën çdo 1500ms
setInterval(() => {
observer.next(i++)
}, 1500);
}).pipe(
debounceTime(700), // Pritni 700ms pĂ«r vlerat para se tâi pĂ«rpunoni
take(3)
); 
Operators â takeWhile

Emiton vlera derisa âtakeWhileâ tĂ« mos kthejĂ« false, pas sĂ« cilĂ«s do tĂ« anullohet nga rrjedha.
const { Observable } = Rx;
const { debounceTime, takeWhile } = RxOperators;
Observable.create((observer) => {
let i = 1;
observer.next(i++);
// Emit value every 1000ms
setInterval(() => {
observer.next(i++)
}, 1000);
}).pipe(
takeWhile(producer => producer < 5)
); 
Operators â combineLatest
Operatori kombinuese combineLatest është disi si promise.all. Ai bashkon disa rrjedha në një të vetme. Pas kësaj, kur çdo rrjedhë të emitojë të paktën një herë, marrim vlerat më të fundit nga secila si një array. Më pas, pas çdo emitoj të ndonjë prej rrjedhave të bashkuara, ai do të kthejë vlera të reja.

const { combineLatest, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Emit value every 1000ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
});
const observer_2 = Observable.create((observer) => {
let i = 1;
// Emit value every 750ms
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
});
combineLatest(observer_1, observer_2).pipe(take(5));
Operators â zip
Zip pret një vlerë nga çdo rrjedhë dhe formon një array mbi këto vlera. Nëse nuk merr një vlerë nga ndonjë rrjedhë, grupi nuk do të formohet.

const { zip, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Emit value every 1000ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
});
const observer_2 = Observable.create((observer) => {
let i = 1;
// Emit value every 750ms
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
});
const observer_3 = Observable.create((observer) => {
let i = 1;
// Emit value every 500ms
setInterval(() => {
observer.next('c: ' + i++);
}, 500);
});
zip(observer_1, observer_2, observer_3).pipe(take(5));
Operators â forkJoin
forkJoin gjithashtu bashkon rrjedhat, por emiton një vlerë vetëm kur të gjitha rrjedhat të kenë përfunduar (complete).

const { forkJoin, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Emit value every 1000ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
}).pipe(take(3));
const observer_2 = Observable.create((observer) => {
let i = 1;
// Emit value every 750
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
}).pipe(take(5));
const observer_3 = Observable.create((observer) => {
let i = 1;
// Emit value every 500
setInterval(() => {
observer.next('c: ' + i++);
}, 500);
}).pipe(take(4));
forkJoin(observer_1, observer_2, observer_3);
Operators â map
Operatori transformues map ndryshon vlerën e emisionit në një të re.

const { Observable } = Rx;
const { take, map } = RxOperators;
Observable.create((observer) => {
let i = 1;
// Emit value every 1000ms
setInterval(() => {
observer.next(i++);
}, 1000);
}).pipe(
map(x => x * 10),
take(3)
);

Operators â share, tap
Operatori tap lejon të krijojë efekte anësore, që do të thotë ndonjë veprim që nuk ndikon në rendin e sekuencës.
Operatori utilitar share është në gjendje të bëjë një rrjedhë të ftohtë të nxehtë.

Kemi përfunduar me operatorët. Tani do të kalojmë te Subject.
Mendime në zë
Dhe kĂ«tu shkova tĂ« pi çaj. MĂ« lodhĂ«n kĂ«to shembuj đ
Familja e subject-ëve
Familja e subject-ëve është një shembull e qartë e rrjedhave të nxehta. Këto klasa janë një hibrid, që veprojnë si observable dhe observer njëkohësisht. Duke qenë se subject është një rrjedhë e nxehtë, është e nevojshme të hiqet dorë nga të. Në lidhje me metodat kryesore, këto janë:
- next â transmetimi i tĂ« dhĂ«nave tĂ« reja nĂ« rrjedhĂ«
- error â gabim dhe pĂ«rfundimi i rrjedhĂ«s
- complete â pĂ«rfundimi i rrjedhĂ«s
- subscribe â regjistrohu nĂ« rrjedhĂ«
- unsubscribe â hiq dorĂ« nga rrjedha
- asObservable â transformohet nĂ« njĂ« vĂ«zhgues
- toPromise â transformon nĂ« njĂ« premtim
Dallohen 4-5 lloje subject-ësh.
Mendime në zë
Në stream thashë 4, por duket se ata kanë shtuar një tjetër. Siç thonë, jetoni dhe mësoni.
Subject e thjeshtĂ« new Subject()â lloji mĂ« i thjeshtĂ« i subject-Ă«ve. Krijohet pa parameter. Transmeton vlerat qĂ« kanĂ« ardhur vetĂ«m pas regjistrimit.
BehaviorSubject new BehaviorSubject(defaultData) â sipas mendimit tim, lloji mĂ« i zakonshĂ«m i subject-Ă«ve. Merr njĂ« vlerĂ« tĂ« paracaktuar. Ruajti gjithmonĂ« tĂ« dhĂ«nat e emitit tĂ« fundit, tĂ« cilat i transmeton gjatĂ« regjistrimit. Kjo klasĂ« gjithashtu ka njĂ« metodĂ« tĂ« dobishme value, e cila kthen vlerĂ«n aktuale tĂ« rrjedhĂ«s.
ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) â Mund tĂ« pranojĂ« opcionalisht si argument tĂ« parĂ« madhĂ«sinĂ« e buffers qĂ« do tĂ« mbajĂ«, dhe si tĂ« dytĂ« kohĂ«n gjatĂ« sĂ« cilĂ«s na duhet ndryshimi.
AsyncSubject new AsyncSubject() â gjatĂ« regjistrimit nuk ndodh asgjĂ«, dhe vlera do tĂ« kthehet vetĂ«m kur tĂ« pĂ«rfundojĂ«. Do tĂ« kthehet vetĂ«m vlera e fundit e rrjedhĂ«s.
WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig | Observable, destination?: Observer) â Dokumentacioni pĂ«r tĂ« hesht dhe unĂ« e shoh pĂ«r herĂ« tĂ« parĂ«. Kush di se çfarĂ« bĂ«n, na shkruani pĂ«r ta plotĂ«suar.
Uff. Kështu ne kemi shqyrtuar gjithçka që doja të flisja sot. Shpresoj që kjo informacion të ishte e dobishme. Mund ta shqyrtoni listën e literaturës në seksionin e informacionit të dobishëm.
Informacion i dobishëm
- â linku pĂ«r stream
- â Si funksionon JS: njĂ« pĂ«rmbledhje e motorit, mekanizmave tĂ« ekzekutimit, dhe stack tĂ« thirrjeve.
- â Si si punon JS: cikli i ngjarjeve, asinkronia dhe pesĂ« mĂ«nyra pĂ«r tĂ« pĂ«rmirĂ«suar kodin me async / await
- â Si punon Event Loop nĂ« JavaScript
- â Evolucioni i JavaScript asinkron
- â ĂfarĂ« Ă«shtĂ« RxJS dhe pse Ă«shtĂ« e dobishme ta dish
- â PĂ«rdorimi praktik i RxJS
- â Tutoriali i RxJS Observables â Krijimi & Abonimi nĂ« Observables
- â RXJS: Observables tĂ« Ngrohta dhe tĂ« Ftohta
- â Klasa, funksione pĂ«r krijimin e Observable. OperatorĂ«t.
- â OperatorĂ«t e RxJS me shembuj
- â Lista e API-ve
- â Llojet e Subject dhe Planifikimi nĂ« RxJS
Burimi: habr.com
