Ósamstilltur forritun í JavaScript (Callback, Promise, RxJs)
Hæ allir. Hafðu samband Omelnitsky Sergey. Fyrir ekki svo löngu síðan hýsti ég straum um hvarfgjarna forritun, þar sem ég talaði um ósamstillingu í JavaScript. Í dag langar mig að draga þetta efni saman.
En áður en við byrjum á aðalefninu þurfum við að gera kynningu. Svo skulum við byrja á skilgreiningum: hvað eru stafla og biðröð?
Stafli er safn þar sem þættirnir eru sóttir á „síðast inn, fyrst út“ LIFO grundvelli
Biðröð er safn þar sem þættirnir eru fengnir samkvæmt meginreglunni („fyrstur inn, fyrst út“ FIFO
Allt í lagi, við skulum halda áfram.
JavaScript er forritunarmál með einum þræði. Þetta þýðir að það hefur aðeins einn þráð af framkvæmd og einn stafla þar sem aðgerðir eru í biðröð fyrir framkvæmd. Þess vegna getur JavaScript aðeins framkvæmt eina aðgerð í einu, á meðan aðrar aðgerðir munu bíða eftir röð á staflanum þar til þær eru kallaðar.
Símtalastakki er gagnastrúktúr sem á einfaldan hátt skráir upplýsingar um staðinn í forritinu þar sem við erum. Ef við hoppum inn í fall ýtum við innkomu hennar efst í staflann. Þegar við komum til baka frá falli, skjótum við efsta þættinum úr staflanum og endum þar sem við kölluðum þetta fall frá. Það er allt sem staflinn getur gert. Og nú mjög áhugaverð spurning. Hvernig virkar þá ósamstilling í JavasScript?
Reyndar, auk staflans, hafa vafrar sérstaka biðröð til að vinna með svokallaða WebAPI. Aðgerðir úr þessari biðröð verða framkvæmdar í röð eftir að staflan er alveg hreinsuð. Aðeins eftir það eru þeir settir úr röðinni á stafla til framkvæmdar. Ef það er að minnsta kosti einn þáttur á bunkanum í augnablikinu, þá komast þau ekki á bunkann. Bara vegna þessa er oft ónákvæmt að kalla aðgerðir eftir tímamörkum í tíma, þar sem aðgerðin kemst ekki úr biðröðinni í stafla á meðan hún er full.
Við skulum skoða eftirfarandi dæmi og fara í gegnum það skref fyrir skref. Við skulum líka sjá hvað gerist í kerfinu.
1) Ekkert er að gerast ennþá. Vafraborðið er hreint, símtalastaflinn er tómur.
2) Síðan er skipuninni console.log('Hi') bætt við kallstaflann.
3) Og það er uppfyllt
4) Þá er console.log('Hi') fjarlægður úr símtalstaflanum.
5) Nú skulum við halda áfram að skipuninni setTimeout(fall cb1() {… }). Það er bætt við símtalabunkann.
6) Skipunin setTimeout(fall cb1() {… }) er keyrð. Vafrinn býr til tímamæli sem er hluti af vef API. Það mun framkvæma niðurtalningu.
7) Skipunin setTimeout(fall cb1() {… }) hefur lokið verki sínu og er fjarlægð úr kallastaflanum.
8) console.log('Bye') skipuninni er bætt við kallstaflann.
9) Skipunin console.log('Bye') er keyrð.
10) Skipunin console.log('Bye') er fjarlægð úr símtalsbunkanum.
11) Eftir að að minnsta kosti 5000 ms eru liðnar lýkur tímamælirinn og setur cb1 svarhringinguna í svarhringingarröðina.
12) Atburðalykkjan tekur fall cb1 úr svarhringingarröðinni og ýtir henni inn í símtalabunkann.
13) Cb1 aðgerðin er keyrð og bætir console.log('cb1') við kallstaflann.
14) Skipunin console.log('cb1') er keyrð.
15) Skipunin console.log('cb1') er fjarlægð úr kallastaflanum.
16) Aðgerð cb1 er fjarlægð úr símtalsbunkanum.
Við skulum skoða dæmi í gangfræði:
Jæja, við skoðuðum hvernig ósamstilling er útfærð í JavaScript. Nú skulum við tala stuttlega um þróun ósamstilltra kóða.
Þróun ósamstilltra kóða.
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);
})
})
})
})
})
});
Ósamstillt forritun eins og við þekkjum hana í JavaScript er aðeins hægt að útfæra með aðgerðum. Hægt er að senda þær eins og allar aðrar breytur yfir í aðrar aðgerðir. Svona fæddust hringingar. Og það er flott, skemmtilegt og fjörugt, þar til það breytist í sorg, depurð og sorg. Hvers vegna? Það er einfalt:
Eftir því sem flókinn kóðann eykst breytist verkefnið fljótt í óljósar margar hreiðrar blokkir - „tilbakahringingarhelvíti“.
Auðvelt er að líta framhjá villumeðferð.
Þú getur ekki skilað tjáningum með return.
Með tilkomu Promise hefur ástandið orðið aðeins betra.
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);
});
Loforðakeðjur komu fram sem bættu læsileika kóðans
Það var sérstök aðferð til að hlera villur
Samhliða framkvæmd með Promise.all bætt við
Við getum leyst hreiður ósamstilling með því að nota ósamstillingu/bíður
En loforðið hefur sínar takmarkanir. Til dæmis er ekki hægt að hætta við loforð, án þess að dansa við bumbur, og síðast en ekki síst, það virkar með einu gildi.
Jæja, hér erum við að nálgast hvarfgjarna forritun. Þreyttur? Jæja, það góða er að þú getur farið að brugga nokkra máva, heilastormað og farið aftur til að lesa meira. Og ég mun halda áfram.
Reactive forritun - forritunaraðferð sem beinist að gagnaflæði og útbreiðslu breytinga. Við skulum skoða nánar hvað gagnastraumur er.
// Получаем ссылку на элемент
const input = ducument.querySelector('input');
const eventsArray = [];
// Пушим каждое событие в массив eventsArray
input.addEventListener('keyup',
event => eventsArray.push(event)
);
Ímyndum okkur að við höfum innsláttarreit. Við búum til fylki og fyrir hverja takkauppfærslu inntaksviðburðarins munum við geyma atburðinn í fylkinu okkar. Jafnframt vil ég taka fram að fylkið okkar er raðað eftir tíma, þ.e. vísitala síðari atburða er hærri en vísitala fyrri atburða. Slík fylki er einfaldað gagnaflæðislíkan, en það er ekki enn flæði. Til þess að þetta fylki sé örugglega kallað straumur verður það að geta á einhvern hátt upplýst áskrifendur um að ný gögn séu komin í hann. Þannig komum við að skilgreiningunni á flæði.
Rennsli er gagnaflokkur flokkaður eftir tíma sem getur gefið til kynna að gögnin hafi breyst. Ímyndaðu þér núna hversu þægilegt það verður að skrifa kóða þar sem þú þarft að kalla fram nokkra atburði í mismunandi hlutum kóðans fyrir eina aðgerð. Við gerumst einfaldlega áskrifendur að straumnum og hann mun segja okkur hvenær breytingar verða. Og RxJs bókasafnið getur gert þetta.
RxJS er bókasafn til að vinna með ósamstillt og atburðabundið forrit með því að nota sjáanlegar raðir. Bókasafnið veitir aðalgerðina Athuganlegt, nokkrar hjálpargerðir (Áheyrnarfulltrúar, dagskrárgerðarmenn, viðfangsefni) og rekstraraðila til að vinna með viðburði eins og með söfn (korta, sía, minnka, hvert og svipaðar frá JavaScript Array).
Við skulum skilja grunnhugtök þessa bókasafns.
Áberandi, áhorfandi, framleiðandi
Athuganleg er fyrsta grunntegundin sem við munum skoða. Þessi flokkur inniheldur meginhluta RxJs útfærslunnar. Það er tengt straumi sem hægt er að fylgjast með, sem hægt er að gerast áskrifandi að með áskriftaraðferðinni.
Observable útfærir hjálparkerfi til að búa til uppfærslur, svokallaða Observer. Uppspretta gilda fyrir áheyrnarfulltrúa er kallað Leikstjóri. Það getur verið fylki, endurtekning, veftengi, einhvers konar atburður osfrv. Svo við getum sagt að observable sé leiðari milli framleiðanda og áheyrnarfulltrúa.
Observable sér um þrjár tegundir af Observer atburðum:
næsta - ný gögn
villa – villa ef röðinni lauk vegna undantekningar. þessi atburður felur einnig í sér að röðinni sé lokið.
lokið — merki um að röðinni sé lokið. Þetta þýðir að ekki verða fleiri ný gögn.
Við skulum sjá kynningu:
Í upphafi munum við vinna úr gildunum 1, 2, 3 og eftir 1 sek. við fáum 4 og endum þráðinn okkar.
Að hugsa upphátt
Og svo áttaði ég mig á því að það var áhugaverðara að segja frá en að skrifa um það. 😀
Áskrift
Þegar við gerumst áskrifendur að straumi búum við til nýjan flokk Áskrift, sem gefur okkur möguleika á að segja upp áskrift með aðferðinni afskrá. Við getum líka flokkað áskriftir með aðferðinni bæta við. Jæja, það er rökrétt að við getum sundrað þræði með því að nota fjarlægja. Bæta við og fjarlægja aðferðir samþykkja aðra áskrift sem inntak. Ég vil taka það fram að þegar við segjum upp áskrift þá afskrifum við allar barnaáskriftir eins og þær kölluðu líka afskráningaraðferðina. Gjörðu svo vel.
Tegundir strauma
HOT
KALT
Framleiðandi er skapaður utan hins sjáanlega
Framleiðandi er búin til inni sjáanleg
Gögn eru send á þeim tíma sem sjáanlegt er búið til
Gögn eru veitt við áskrift.
Þarf meiri rökfræði til að segja upp áskrift
Þráðurinn lýkur af sjálfu sér
Notar eitt-á-marga samband
Notar einstaklingssamband
Allar áskriftir hafa sömu merkingu
Áskriftir eru sjálfstæðar
Gögn geta glatast ef engin áskrift er
Endurútgefur öll straumgildi fyrir nýja áskrift
Til að gefa samlíkingu myndi ég ímynda mér heitan straum eins og kvikmynd í kvikmyndahúsi. Á hvaða tímapunkti þú komst, frá þeirri stundu byrjaðir þú að horfa. Ég myndi bera saman kalt straum við símtal í þeim. stuðning. Allir sem hringja hlusta á upptöku símsvarans frá upphafi til enda, en þú getur lagt á með afskráningu.
Ég vil taka það fram að það eru líka svokallaðir hlýir lækir (ég hef hitt slíka skilgreiningu afar sjaldan og aðeins í erlendum samfélögum) - þetta er lækur sem breytist úr köldum straumi í heitan. Spurningin vaknar - hvar á að nota)) Ég mun gefa dæmi frá æfingum.
Ég er að vinna með Angular. Hann notar rxjs virkan. Til að fá gögn á netþjóninn býst ég við köldum straumi og ég nota þennan straum í sniðmátinu með asyncPipe. Ef ég nota þessa pípu nokkrum sinnum, þá, aftur í skilgreininguna á köldum straumi, mun hver pípa biðja um gögn frá þjóninum, sem er vægast sagt skrítið. Og ef ég breyti köldum straumi í heitan, þá mun beiðnin gerast einu sinni.
Almennt séð er nokkuð erfitt fyrir byrjendur að skilja tegund flæðis, en mikilvægt.
Stjórnandi
return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
.pipe(
tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
map(({ data }: TradeCompanyList) => data)
);
Rekstraraðilar veita okkur tækifæri til að vinna með strauma. Þeir hjálpa til við að stjórna atburðunum sem streyma í Observable. Við munum íhuga nokkra af þeim vinsælustu og frekari upplýsingar um rekstraraðilana er að finna á krækjunum í gagnlegum upplýsingum.
Rekstraraðilar-af
Við skulum byrja með hjálpar rekstraraðila. Það býr til Observable byggt á einföldu gildi.
Rekstraraðilar - sía
Síustjórinn, eins og nafnið gefur til kynna, síar straummerkið. Ef stjórnandinn skilar satt, þá sleppur hann lengra.
Rekstraraðilar - taka
taka - Tekur gildi fjölda losunar, eftir það lýkur straumnum.
Operators-debounceTime
debounceTime - fleygir útsendum gildum sem falla innan tilgreinds tímabils milli úttaksgagna - eftir að tímabilið er liðið, gefur frá sér síðasta gildið.
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)
);
Rekstraraðilar-taka á meðan
Sendir gildi þar til takeWhile skilar false, eftir það segir það upp áskrift að þræðinum.
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 )
);
Operators-combineNýjasta
Sameinaði rekstraraðilinn combineLatest er nokkuð svipaður og lofa.allt. Það sameinar marga strauma í einn. Eftir að hver þráður hefur gefið að minnsta kosti eina útsendingu fáum við nýjustu gildin frá hverjum sem fylki. Ennfremur, eftir hvers kyns losun frá sameinuðu straumunum, mun það gefa ný gildi.
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));
Operators-zip
Zip - bíður eftir gildi úr hverjum straumi og myndar fylki sem byggir á þessum gildum. Ef gildið kemur ekki frá neinum þræði, þá verður hópurinn ekki myndaður.
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));
Rekstraraðilar - forkJoin
forkJoin tengir einnig þræði, en það gefur aðeins frá sér gildi þegar allir þræðir eru búnir.
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);
Rekstrarkort
Stjórnandi kortabreytingar umbreytir losunargildinu í nýtt.
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)
);
Rekstraraðilar - deildu, pikkaðu á
Kranatæki gerir þér kleift að gera aukaverkanir, það er að segja allar aðgerðir sem hafa ekki áhrif á röðina.
Rekstraraðili hlutaveitunnar getur breytt köldum straumi í heitan.
Rekstraraðilar eru búnir. Við skulum halda áfram í Subject.
Að hugsa upphátt
Og svo fór ég að drekka te. Ég er þreytt á þessum dæmum 😀
Efnisfjölskylda
Viðfangsefnafjölskyldan er gott dæmi um heita þræði. Þessir flokkar eru eins konar blendingar sem virka sem áhorfandi og áhorfandi á sama tíma. Þar sem viðfangsefnið er heitur straumur verður að segja upp áskrift að því. Ef við tölum um helstu aðferðir, þá eru þessar:
næst - að senda ný gögn til straumsins
villa - villa og þráðarslit
lokið - enda þráðarins
gerast áskrifandi - gerast áskrifandi að straumi
afskrá - afskráðu þig af straumnum
asobservable - umbreytast í áhorfanda
toPromise - breytist í loforð
Úthlutaðu 4 5 tegundum viðfangsefna.
Að hugsa upphátt
Ég sagði 4 á straumnum, en það kom í ljós að þeir bættu einum við. Eins og orðatiltækið segir, lifðu og lærðu.
Einfalt efni new Subject()- einfaldasta tegund viðfangsefna. Búið til án breytu. Stendur gildin sem komu aðeins eftir áskriftina.
Hegðun Viðfangsefni new BehaviorSubject( defaultData<T> ) - að mínu mati algengasta tegund námsgreina. Inntakið tekur sjálfgefið gildi. Vistar alltaf gögn síðasta tölublaðs, sem eru send við áskrift. Þessi flokkur hefur einnig gagnlega gildisaðferð sem skilar núverandi gildi straumsins.
EndurspilunSubject new ReplaySubject(bufferSize?: number, windowTime?: number) - Valfrjálst getur það tekið sem fyrstu röksemd stærðar biðminni gilda sem það mun geyma í sjálfu sér og í seinna skiptið þar sem við þurfum breytingar.
ósamstillt viðfangsefni new AsyncSubject() - ekkert gerist þegar þú gerist áskrifandi og gildið verður aðeins skilað þegar því er lokið. Aðeins síðasta gildi straumsins verður skilað.
WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - Skjölin eru þögul um það og ég sé það sjálfur í fyrsta skipti. Hver veit hvað hann gerir, skrifaðu, bætum við.
Púff. Jæja, við höfum íhugað allt sem ég vildi segja í dag. Vona að þessar upplýsingar hafi verið gagnlegar. Þú getur lesið listann yfir bókmenntir á eigin spýtur í flipanum Gagnlegar upplýsingar.