Rhaglennu anghydamserol yn JavaScript (Galwad yn ôl, Addewid, RxJs)
Helo i gyd. Mewn cysylltiad Omelnitsky Sergey. Ddim mor bell yn ôl, cynhaliais ffrwd ar raglennu adweithiol, lle siaradais am asyncroniaeth yn JavaScript. Heddiw hoffwn grynhoi'r deunydd hwn.
Ond cyn i ni ddechrau'r prif ddeunydd, mae angen inni wneud cyflwyniad. Felly gadewch i ni ddechrau gyda diffiniadau: beth yw stac a chiw?
Pentwr yn gasgliad y mae ei elfennau yn cael eu hadalw ar sail LIFO “olaf i mewn, cyntaf allan”.
Ciw yn gasgliad y ceir ei elfennau yn ôl yr egwyddor (“cyntaf i mewn, cyntaf allan”) FIFO
Iawn, gadewch i ni barhau.
Mae JavaScript yn iaith raglennu un edau. Mae hyn yn golygu mai dim ond un edefyn gweithredu sydd ganddo ac un pentwr lle mae swyddogaethau'n cael eu ciwio i'w cyflawni. Felly, dim ond un llawdriniaeth y gall JavaScript ei chyflawni ar y tro, tra bydd gweithrediadau eraill yn aros eu tro ar y pentwr nes eu bod yn cael eu galw.
Ffoniwch y pentwr yn strwythur data sydd, mewn termau syml, yn cofnodi gwybodaeth am y lle yn y rhaglen lle rydym ni. Os ydym yn neidio i mewn i swyddogaeth, rydym yn gwthio ei mynediad i ben y pentwr. Pan fyddwn yn dychwelyd o swyddogaeth, rydyn ni'n popio'r elfen uchaf o'r pentwr ac yn dod i ben o ble rydyn ni'n galw'r swyddogaeth hon. Dyna'r cyfan y gall y pentwr ei wneud. Ac yn awr cwestiwn diddorol iawn. Sut felly mae asyncroni yn gweithio yn JavasScript?
Mewn gwirionedd, yn ogystal â'r pentwr, mae gan borwyr giw arbennig ar gyfer gweithio gyda'r hyn a elwir yn WebAPI. Bydd swyddogaethau o'r ciw hwn yn cael eu cyflawni mewn trefn dim ond ar ôl i'r pentwr gael ei glirio'n llwyr. Dim ond ar ôl hynny y cânt eu gosod o'r ciw i'r pentwr i'w gweithredu. Os oes o leiaf un elfen ar y pentwr ar hyn o bryd, yna ni allant fynd ar y pentwr. Dim ond oherwydd hyn, mae galw swyddogaethau erbyn terfyn amser yn aml yn anghywir o ran amser, gan na all y swyddogaeth fynd o'r ciw i'r pentwr tra ei fod yn llawn.
Gadewch i ni edrych ar yr enghraifft ganlynol a gadewch i ni fynd drwyddo gam wrth gam. Gadewch i ni hefyd weld beth sy'n digwydd yn y system.
1) Hyd yn hyn nid oes dim yn digwydd. Mae consol y porwr yn lân, mae'r pentwr galwadau yn wag.
2) Yna mae'r consol gorchymyn.log ('Hi') yn cael ei ychwanegu at y pentwr galwadau.
3) Ac mae'n cael ei gyflawni
4) Yna consol.log ('Helo') yn cael ei dynnu o'r pentwr galwadau.
5) Nawr, gadewch i ni symud ymlaen i'r gorchymyn setTimeout (function cb1 () {… }). Mae'n cael ei ychwanegu at y pentwr galwadau.
6) Gweithredir y gorchymyn setTimeout (swyddogaeth cb1 () {… }). Mae'r porwr yn creu amserydd sy'n rhan o'r Web API. Bydd yn perfformio cyfrif i lawr.
7) Mae'r gorchymyn setTimeout (swyddogaeth cb1 () {… }) wedi cwblhau ei waith ac yn cael ei dynnu o'r pentwr galwadau.
8) Mae'r gorchymyn console.log ('Bye') yn cael ei ychwanegu at y pentwr galwadau.
9) Gweithredir y gorchymyn console.log ('Bye').
10) Mae'r gorchymyn console.log ('Bye') yn cael ei dynnu o'r pentwr galwadau.
11) Ar ôl i o leiaf 5000ms ddod i ben, mae'r amserydd yn dod i ben ac yn rhoi'r alwad yn ôl cb1 yn y ciw galw'n ôl.
12) Mae'r ddolen digwyddiad yn cymryd swyddogaeth cb1 o'r ciw galw'n ôl ac yn ei gwthio i'r pentwr galwadau.
13) Mae'r swyddogaeth cb1 yn cael ei gweithredu ac yn ychwanegu consol.log ('cb1') i'r pentwr galwadau.
14) Gweithredir y gorchymyn console.log ('cb1').
15) Mae'r consol gorchymyn.log ('cb1') yn cael ei dynnu o'r pentwr galwadau.
16) Mae swyddogaeth cb1 yn cael ei dynnu o'r pentwr galwadau.
Edrychwn ar enghraifft mewn dynameg:
Wel, fe wnaethon ni edrych ar sut mae asyncroni yn cael ei weithredu yn JavaScript. Nawr, gadewch i ni siarad yn fyr am esblygiad cod asyncronig.
Esblygiad cod asyncronaidd.
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);
})
})
})
})
})
});
Dim ond gyda swyddogaethau y gellir gwneud rhaglennu anghydamserol fel y gwyddom amdano yn JavaScript. Gellir eu trosglwyddo fel unrhyw newidyn arall i swyddogaethau eraill. Dyma sut y ganwyd galwadau yn ôl. Ac mae'n cŵl, yn hwyl ac yn frwd, nes iddo droi'n dristwch, melancholy a thristwch. Pam? Ydy, mae'n syml:
Wrth i gymhlethdod y cod dyfu, mae'r prosiect yn troi'n gyflym yn flociau nythu lluosog aneglur - “callback uffern”.
Gellir diystyru trin gwallau yn hawdd.
Ni allwch ddychwelyd ymadroddion gyda dychwelyd.
Gyda dyfodiad Addewid, mae'r sefyllfa wedi gwella ychydig.
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);
});
Ymddangosodd cadwyni addewid, a oedd yn gwella darllenadwyedd y cod
Roedd dull ar wahân o ryng-gipio gwallau
Ychwanegwyd gweithrediad cyfochrog ag Promise.all
Gallwn ddatrys asynchrony nythu gyda async/aros
Ond mae gan yr addewid ei chyfyngiadau. Er enghraifft, ni ellir canslo addewid, heb ddawnsio â thambwrîn, ac yn bwysicaf oll, mae'n gweithio gydag un gwerth.
Wel, dyma ni'n dynesu at raglennu adweithiol yn ddidrafferth. Wedi blino? Wel, y peth da yw, gallwch chi fynd i fragu gwylanod, taflu syniadau a dychwelyd i ddarllen mwy. A byddaf yn parhau.
Rhaglennu adweithiol - patrwm rhaglennu sy'n canolbwyntio ar lif data a lledaeniad newidiadau. Gadewch i ni edrych yn agosach ar beth yw llif data.
// Получаем ссылку на элемент
const input = ducument.querySelector('input');
const eventsArray = [];
// Пушим каждое событие в массив eventsArray
input.addEventListener('keyup',
event => eventsArray.push(event)
);
Gadewch i ni ddychmygu bod gennym faes mewnbwn. Rydym yn creu arae, ac ar gyfer pob keyup o'r digwyddiad mewnbwn, byddwn yn storio'r digwyddiad yn ein casgliad. Ar yr un pryd, hoffwn nodi bod ein casgliad yn cael ei ddidoli yn ôl amser, h.y. mae'r mynegai o ddigwyddiadau diweddarach yn fwy na'r mynegai o ddigwyddiadau cynharach. Mae arae o'r fath yn fodel llif data wedi'i symleiddio, ond nid yw'n llif eto. Er mwyn i'r arae hon gael ei galw'n ffrwd yn ddiogel, rhaid iddo allu hysbysu tanysgrifwyr rywsut bod data newydd wedi cyrraedd ynddo. Felly rydym yn dod at y diffiniad o lif.
Llif yn amrywiaeth o ddata wedi'u didoli yn ôl amser a all ddangos bod y data wedi newid. Nawr dychmygwch pa mor gyfleus yw hi i ysgrifennu cod lle mae angen i chi sbarduno sawl digwyddiad mewn gwahanol rannau o'r cod ar gyfer un weithred. Yn syml, rydym yn tanysgrifio i'r ffrwd a bydd yn dweud wrthym pan fydd newidiadau'n digwydd. A gall llyfrgell RxJs wneud hyn.
RxJS yn llyfrgell ar gyfer gweithio gyda rhaglenni asyncronaidd a seiliedig ar ddigwyddiadau gan ddefnyddio dilyniannau gweladwy. Mae'r llyfrgell yn darparu'r prif fath Arsylwi, sawl math o gynorthwyydd (Sylwedyddion, Trefnwyr, Testunau) a gweithredwyr ar gyfer gweithio gyda digwyddiadau fel gyda chasgliadau (map, hidlo, lleihau, bob a rhai tebyg o JavaScript Array).
Gadewch i ni ddeall cysyniadau sylfaenol y llyfrgell hon.
Arsylladwy, Sylwedydd, Cynhyrchydd
Arsylladwy yw'r math sylfaen cyntaf y byddwn yn edrych arno. Mae y dosbarth hwn yn cynnwys y prif ran o weithrediad RxJs. Mae'n gysylltiedig â ffrwd arsylladwy, y gellir tanysgrifio iddi gan ddefnyddio'r dull tanysgrifio.
Mae Arsylladwy yn gweithredu mecanwaith ategol ar gyfer creu diweddariadau, yr hyn a elwir Observer. Gelwir ffynhonnell gwerthoedd ar gyfer Sylwedydd Cynhyrchydd. Gall fod yn arae, yn iterator, yn soced gwe, rhyw fath o ddigwyddiad, ac ati. Felly gallwn ddweud bod gweladwy yn arweinydd rhwng Cynhyrchydd a Sylwedydd.
Mae gweladwy yn ymdrin â thri math o ddigwyddiadau Observer:
nesaf - data newydd
error - gwall os daw'r dilyniant i ben oherwydd eithriad. mae'r digwyddiad hwn hefyd yn awgrymu diwedd y dilyniant.
complete - signal am ddiwedd y dilyniant. Mae hyn yn golygu na fydd mwy o ddata newydd
Gawn ni weld demo:
Ar y dechrau byddwn yn prosesu'r gwerthoedd 1, 2, 3, ac ar ôl 1 eiliad. rydym yn cael 4 ac yn gorffen ein llinyn.
Meddwl yn uchel
Ac yna sylweddolais ei fod yn fwy diddorol dweud nag ysgrifennu amdano. 😀
Tanysgrifio
Pan fyddwn yn tanysgrifio i ffrwd, rydym yn creu dosbarth newydd tanysgrifiad, sy'n rhoi'r opsiwn i ni ddad-danysgrifio gyda'r dull dad-danysgrifio. Gallwn hefyd grwpio tanysgrifiadau gan ddefnyddio'r dull ychwanegu. Wel, mae'n rhesymegol y gallwn ddadgrwpio edafedd gan ddefnyddio gwared ar. Mae'r dulliau ychwanegu a dileu yn derbyn tanysgrifiad gwahanol fel mewnbwn. Hoffwn nodi, pan fyddwn yn dad-danysgrifio, ein bod yn dad-danysgrifio o bob tanysgrifiad plentyn fel pe baent hefyd yn galw'r dull dad-danysgrifio. Cer ymlaen.
Mathau o ffrydiau
POETH
OER
Cynhyrchydd yn cael ei greu y tu allan i'r arsylladwy
Cynhyrchydd yn cael ei greu y tu mewn arsylladwy
Mae data'n cael ei basio ar yr adeg y caiff yr arsylladwy ei greu
Darperir data ar adeg y tanysgrifiad.
Angen mwy o resymeg i ddad-danysgrifio
Edau yn terfynu ar ei ben ei hun
Yn defnyddio perthynas un-i-lawer
Yn defnyddio perthynas un-i-un
Mae gan bob tanysgrifiad yr un gwerth
Mae tanysgrifiadau yn annibynnol
Gellir colli data os nad oes tanysgrifiad
Yn ailgyhoeddi holl werthoedd ffrwd ar gyfer tanysgrifiad newydd
I roi cyfatebiaeth, byddwn yn dychmygu ffrwd boeth fel ffilm mewn sinema. Ar ba bwynt mewn amser y daethoch, o'r eiliad honno y dechreuoch wylio. Byddwn yn cymharu ffrwd oer gyda galwad yn y rheini. cefnogaeth. Mae unrhyw alwr yn gwrando ar y peiriant ateb yn recordio o'r dechrau i'r diwedd, ond gallwch chi roi'r ffôn i lawr gyda dad-danysgrifio.
Hoffwn nodi bod yna hefyd ffrydiau cynnes fel y'u gelwir (rwyf wedi cwrdd â diffiniad o'r fath yn hynod anaml a dim ond mewn cymunedau tramor) - mae hon yn nant sy'n trawsnewid o nant oer i un boeth. Mae'r cwestiwn yn codi - ble i ddefnyddio)) Rhoddaf enghraifft o arfer.
Rwy'n gweithio gydag Angular. Defnyddia rxjs yn weithredol. I gael data i'r gweinydd, rwy'n disgwyl ffrwd oer ac rwy'n defnyddio'r ffrwd hon yn y templed gan ddefnyddio asyncPipe. Os byddaf yn defnyddio'r bibell hon sawl gwaith, yna, gan ddychwelyd at y diffiniad o nant oer, bydd pob pibell yn gofyn am ddata gan y gweinydd, sy'n rhyfedd a dweud y lleiaf. Ac os byddaf yn trosi nant oer yn un gynnes, yna bydd y cais yn digwydd unwaith.
Yn gyffredinol, mae deall y math o lif yn eithaf anodd i ddechreuwyr, ond yn bwysig.
Gweithredwyr
return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
.pipe(
tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
map(({ data }: TradeCompanyList) => data)
);
Mae gweithredwyr yn rhoi cyfle i ni weithio gyda ffrydiau. Maent yn helpu i reoli'r digwyddiadau sy'n llifo yn yr Arsylladwy. Byddwn yn ystyried un neu ddau o'r rhai mwyaf poblogaidd, a gellir dod o hyd i ragor o wybodaeth am y gweithredwyr yn y dolenni mewn gwybodaeth ddefnyddiol.
Gweithredwyr-o
Gadewch i ni ddechrau gyda'r gweithredwr cynorthwy-ydd o. Mae'n creu Arsylladwy yn seiliedig ar werth syml.
Gweithredwyr-hidlo
Mae'r gweithredwr hidlo, fel y mae'r enw'n ei awgrymu, yn hidlo'r signal ffrwd. Os bydd y gweithredwr yn dychwelyd yn wir, yna mae'n neidio ymhellach.
Gweithredwyr - cymryd
cymryd - Yn cymryd gwerth nifer yr allyriannau, ac ar ôl hynny mae'r ffrwd yn dod i ben.
Gweithredwyr-debounceTime
debounceTime - yn taflu gwerthoedd a allyrrir sy'n dod o fewn y cyfwng amser penodedig rhwng data allbwn - ar ôl i'r cyfwng amser fynd heibio, yn allyrru'r gwerth olaf.
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)
);
Gweithredwyr-takeWhile
Yn allyrru gwerthoedd tan takeWhile returns false, yna dad-danysgrifio o'r edefyn.
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 )
);
Gweithredwyr-combineLatest
Mae'r gweithredwr cyfunol combineLatest braidd yn debyg i promise.all. Mae'n cyfuno ffrydiau lluosog yn un. Ar ôl i bob edefyn wneud o leiaf un allyriad, rydym yn cael y gwerthoedd diweddaraf o bob un fel arae. Ymhellach, ar ôl unrhyw allyriad o'r ffrydiau cyfun, bydd yn rhoi gwerthoedd newydd.
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));
Gweithredwyr-zip
Zip - yn aros am werth o bob ffrwd ac yn ffurfio arae yn seiliedig ar y gwerthoedd hyn. Os nad yw'r gwerth yn dod o unrhyw edefyn, yna ni fydd y grŵp yn cael ei ffurfio.
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));
Gweithredwyr - fforchJoin
mae forkJoin hefyd yn ymuno ag edafedd, ond dim ond pan fydd yr holl edafedd wedi'u cwblhau y mae'n allyrru gwerth.
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);
Gweithredwyr-map
Mae'r gweithredwr trawsnewid map yn trawsnewid y gwerth allyrru yn un newydd.
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)
);
Gweithredwyr - rhannu, tap
Mae'r gweithredwr tap yn caniatáu ichi wneud sgîl-effeithiau, hynny yw, unrhyw gamau nad ydynt yn effeithio ar y dilyniant.
Gall y gweithredwr cyfleustodau cyfran droi ffrwd oer yn ffrwd boeth.
Gweithredwyr yn cael eu gwneud. Gadewch i ni symud ymlaen i Pwnc.
Meddwl yn uchel
Ac yna es i yfed te. Dwi wedi blino ar yr enghreifftiau yma 😀
Teulu pwnc
Mae'r teulu pwnc yn enghraifft wych o edafedd poeth. Mae'r dosbarthiadau hyn yn fath o hybrid sy'n gweithredu fel arsylladwy a sylwedydd ar yr un pryd. Gan fod y pwnc yn ffrwd boeth, rhaid ei ddad-danysgrifio o. Os byddwn yn siarad am y prif ddulliau, yna dyma:
nesaf - pasio data newydd i'r ffrwd
gwall - gwall a therfyniad edau
complete - diwedd yr edefyn
tanysgrifio - tanysgrifio i ffrwd
dad-danysgrifio - dad-danysgrifio o'r ffrwd
asArsylladwy - trawsnewid yn arsylwr
toPromise - yn trawsnewid yn addewid
Neilltuo 4 5 math o bynciau.
Meddwl yn uchel
Dywedais 4 ar y ffrwd, ond mae'n troi allan maent yn ychwanegu un yn fwy. Fel y dywed y dywediad, byw a dysgu.
Pwnc Syml new Subject()- y math symlaf o bynciau. Wedi'i greu heb baramedrau. Yn pasio'r gwerthoedd a ddaeth yn unig ar ôl y tanysgrifiad.
Pwnc Ymddygiad new BehaviorSubject( defaultData<T> ) - yn fy marn i y math mwyaf cyffredin o bwnc-pynciau. Mae'r mewnbwn yn cymryd y gwerth rhagosodedig. Bob amser yn arbed data'r rhifyn diwethaf, sy'n cael ei drosglwyddo wrth danysgrifio. Mae gan y dosbarth hwn hefyd ddull gwerth defnyddiol sy'n dychwelyd gwerth cyfredol y ffrwd.
AilchwaraePwnc new ReplaySubject(bufferSize?: number, windowTime?: number) - Yn ddewisol, gall gymryd fel y ddadl gyntaf maint y byffer o werthoedd y bydd yn eu storio ynddo'i hun, a'r ail dro pan fydd angen newidiadau arnom.
pwnc async new AsyncSubject() - nid oes dim yn digwydd wrth danysgrifio, a dim ond pan fydd wedi'i gwblhau y bydd y gwerth yn cael ei ddychwelyd. Dim ond gwerth olaf y ffrwd fydd yn cael ei ddychwelyd.
WebSocketPwnc new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - Mae'r ddogfennaeth yn dawel amdano ac rydw i fy hun yn ei weld am y tro cyntaf. Pwy a wyr beth a wna, ysgrifena, ychwanegwn.
Phew. Wel, rydym wedi ystyried popeth yr oeddwn am ei ddweud heddiw. Gobeithio bod y wybodaeth hon wedi bod o gymorth. Gallwch ddarllen y rhestr o lenyddiaeth ar eich pen eich hun yn y tab Gwybodaeth Ddefnyddiol.