Programmazione asincrona in JavaScript (Callback, Promise, RxJs)
Salute à tutti. In cuntattu Omelnitsky Sergey. Ùn tantu tempu, aghju ospitu un flussu nantu à a prugrammazione reattiva, induve aghju parlatu di asincronia in JavaScript. Oghje vogliu riassume stu materiale.
Ma prima di principià u materiale principale, avemu bisognu di fà una introduzione. Allora cuminciamu cù e definizione: chì sò stack and queue?
Pila hè una cullizzioni chì l'elementi sò recuperati nantu à una basa LIFO "last in, first out".
Gira hè una cullizzioni chì l'elementi sò ottenuti secondu u principiu ("first in, first out" FIFO
Va bè, cuntinuemu.
JavaScript hè un linguaghju di prugrammazione unicu filatu. Questu significa chì hà solu un filu di esecuzione è una pila induve e funzioni sò in fila per l'esekzione. Dunque, JavaScript pò esse realizatu solu una operazione à u tempu, mentre chì l'altri operazioni aspittàranu u so turnu nantu à a pila finu à ch'elli sò chjamati.
Call stack hè una struttura di dati chì, in termini simplici, registra infurmazioni nantu à u locu in u prugramma induve simu. Se saltamu in una funzione, spinghjemu a so entrata à a cima di a pila. Quandu vultemu da una funzione, popmu l'elementu più altu da a pila è finiscinu induve avemu chjamatu sta funzione. Hè tuttu ciò chì a pila pò fà. È avà una quistione assai interessante. Allora cumu funziona l'asincronia in JavasScript?
In fattu, in più di a pila, i navigatori anu una fila speciale per travaglià cù a chjamata WebAPI. E funzioni di sta fila saranu eseguite in ordine solu dopu chì a pila hè stata completamente sguassata. Solu dopu chì sò posti da a fila nantu à a pila per l'esecuzione. S'ellu ci hè almenu un elementu nantu à a pila in u mumentu, allora ùn ponu micca mette nantu à a pila. Solu per quessa, chjamà funzioni per timeout hè spessu imprecisa in u tempu, postu chì a funzione ùn pò micca passà da a fila à a pila mentre hè piena.
Fighjemu un ochju à l'esempiu seguente è andemu à traversu u passu à passu. Videmu ancu ciò chì succede in u sistema.
1) Finu à avà ùn succede nunda. A cunsola di u navigatore hè pulita, a pila di chjama hè viota.
2) Allora u cumandamentu console.log('Hi') hè aghjuntu à a pila di chjama.
3) È hè cumpletu
4) Allora console.log('Hi') hè eliminatu da a pila di chjama.
5) Avà andemu à u cumandimu setTimeout(function cb1() {… }). Hè aghjuntu à a pila di chjama.
6) U cumandimu setTimeout (funzione cb1 () {… }) hè eseguitu. U navigatore crea un timer chì face parte di l'API Web. Eseguirà un countdown.
7) U cumandamentu setTimeout (funzione cb1 () {… }) hà finitu u so travagliu è hè sguassatu da a pila di chjama.
8) U cumandamentu console.log('Bye') hè aghjuntu à a pila di chjama.
9) U cumandimu console.log('Bye') hè eseguitu.
10) U cumandamentu console.log('Bye') hè sguassatu da a pila di chjama.
11) Dopu chì almenu 5000 ms sò passati, u timer finisce è mette a callback cb1 in a fila di callback.
12) U loop di l'avvenimentu piglia a funzione cb1 da a fila di callback è u spinge nantu à a pila di chjama.
13) A funzione cb1 hè eseguita è aghjunghje console.log('cb1') à a pila di chjama.
14) U cumandimu console.log('cb1') hè eseguitu.
15) U cumandimu console.log('cb1') hè sguassatu da a pila di chjama.
16) A funzione cb1 hè eliminata da a pila di chjama.
Fighjemu un esempiu in dinamica:
Ebbè, avemu vistu cumu l'asincronia hè implementata in JavaScript. Avà parlemu brevemente di l'evoluzione di u codice asincronu.
L'evoluzione di u codice asincronu.
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);
})
})
})
})
})
});
A prugrammazione asincrona cum'è a sapemu in JavaScript pò esse fatta solu cù e funzioni. Puderanu esse passati cum'è qualsiasi altra variabile à altre funzioni. Hè cusì chì sò nati i callbacks. È hè cool, divertente è fervente, finu à ch'ella si trasforma in tristezza, malincunia è tristezza. Perchè? Iè, hè simplice:
Cume a cumplessità di u codice cresce, u prughjettu si trasforma rapidamente in oscuri più blocchi nidificati - "callback hell".
A gestione di l'errore pò esse facilmente trascurata.
Ùn pudete micca rinvià espressioni cù ritornu.
Cù l'avventu di Promise, a situazione hè diventata un pocu megliu.
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);
});
I catene di prumessa apparsu, chì migliurà a leghjibilità di u codice
Ci era un metudu separatu di interceptazione di l'errori
Ma a prumessa hà e so limitazioni. Per esempiu, una prumessa, senza ballà cù un tamburinu, ùn pò esse annullata, è più impurtante, travaglia cun un valore.
Ebbè, quì simu avvicinendu bè a prugrammazione reattiva. Stancu ? Ebbè, u bonu hè, pudete andà à fà un pocu di gabbiani, brainstorming è vultà à leghje più. È continueraghju.
Prugrammazione reattiva - un paradigma di prugrammazione focu annantu à i flussi di dati è a propagazione di cambiamenti. Fighjemu un ochju più vicinu à ciò chì hè un flussu di dati.
// Получаем ссылку на элемент
const input = ducument.querySelector('input');
const eventsArray = [];
// Пушим каждое событие в массив eventsArray
input.addEventListener('keyup',
event => eventsArray.push(event)
);
Imaginemu chì avemu un campu di input. Creemu un array, è per ogni keyup di l'avvenimentu di input, almacenaremu l'avvenimentu in u nostru array. À u listessu tempu, vogliu nutà chì u nostru array hè ordinatu per u tempu, i.e. l'indici di l'avvenimenti successivi hè più grande di l'indici di i primi. Un tali array hè un mudellu di flussu di dati simplificatu, ma ùn hè ancu un flussu. Per chì sta matrice sia chjamata in modu sicuru un flussu, deve esse capace d'infurmà à l'abbonati chì novi dati sò ghjunti in questu. Cusì venemu à a definizione di flussu.
Flussu hè una serie di dati ordinati per u tempu chì ponu indicà chì i dati sò cambiati. Avà imaginate quantu cunvene à scrive codice in quale avete bisognu di attivà parechji avvenimenti in diverse parti di u codice per una azione. Simplicemu abbonate à u flussu è ci dicerà quandu i cambiamenti accadenu. È a biblioteca RxJs pò fà questu.
RxJS hè una biblioteca per travaglià cù prugrammi asincroni è basati nantu à l'avvenimenti chì utilizanu sequenze osservabili. A biblioteca furnisce u tipu principale Osservabile, parechji tipi di helper (Osservatori, Schedulers, Sugetti) è operatori per travaglià cù avvenimenti cum'è cù cullezzione (mappa, filtru, riduce, ogni è simili da JavaScript Array).
Capemu i cuncetti basi di sta biblioteca.
Osservatore, Osservatore, Produttore
L'observable hè u primu tipu di basa chì guardemu. Questa classa cuntene a parte principale di l'implementazione RxJs. Hè assuciatu cù un flussu observable, chì pò esse abbonatu cù u metudu subscribe.
Observable implementa un mecanismu ausiliariu per creà aghjurnamenti, u cusì chjamatu Uttinutu. A fonte di i valori per un Osservatore hè chjamatu Producer. Pò esse un array, un iteratore, un socket web, un tipu d'avvenimentu, etc. Allora pudemu dì chì l'observable hè un cunduttore trà Producer è Observer.
Observable gestisce trè tippi di avvenimenti Observer:
prossimu - dati novi
errore - un errore se a sequenza terminata per una eccezzioni. questu avvenimentu implica ancu a fine di a sequenza.
cumpletu - un signalu nantu à a fine di a sequenza. Questu significa chì ùn ci saranu più dati novi
Videmu una demo:
À u principiu, processeremu i valori 1, 2, 3, è dopu à 1 sec. avemu 4 è finiscinu u nostru filu.
Pensendu à voce alta
È tandu aghju capitu chì era più interessante di dì chì di scrive. 😀
abbunamentu
Quandu avemu sottumessi à un flussu, creamu una nova classa abbunamentu, chì ci dà l'opzione di annullà cù u metudu unsubscribe. Pudemu ancu gruppu abbonamenti cù u metudu aghjunghje. Ebbè, hè logicu chì pudemu unaggregate i fili usendu smarisce. I metudi di aghjunghje è sguassate accettanu un abbunamentu diversu cum'è input. Vogliu nutà chì quandu avemu unsubscribe, avemu unsubscribe da tutti l'abbonamenti di i zitelli cum'è s'elli chjamanu ancu u metudu di unsubscribe. Avanti.
Tipi di flussi
HOT
FROIDU
U pruduttore hè creatu fora di l'observable
U pruduttore hè creatu in l'osservabile
I dati sò passati à u mumentu chì l'observable hè creatu
I dati sò furniti à u mumentu di l'abbonamentu.
Avete bisognu di più logica per annunzià l'abbonamentu
U filu finisce da sè stessu
Aduprà una relazione unu à parechji
Aduprà una relazione unu à unu
Tutti l'abbonamenti anu u listessu valore
L'abbonamenti sò indipendenti
I dati ponu esse persi s'ellu ùn ci hè micca abbunamentu
Riemette tutti i valori di flussu per un novu abbonamentu
Per dà una analogia, mi imaginassi un flussu caldu cum'è un filmu in un cinema. À chì puntu in u tempu hè vinutu, da quellu mumentu avete cuminciatu à fighjà. Puderia paragunà un flussu fretu cù una chjama in quelli. sustegnu. Qualchese chjamante ascolta a registrazione di a risponditrice da u principiu à a fine, ma pudete chjappà cun unsubscribe.
Vogliu nutà chì ci sò ancu chjamati flussi caldi (aghju scontru una tale definizione assai raramente è solu in e cumunità straniere) - questu hè un flussu chì si trasforma da un flussu friddu à un caldu. A quistione sorge - induve aduprà)) Daraghju un esempiu da a pratica.
Aghju travagliatu cù Angular. Utiliza attivamente rxjs. Per uttene dati à u servitore, aghju aspittatu un flussu friddu è aghju utilizatu stu flussu in u mudellu cù asyncPipe. Sè aduprate sta pipa parechje volte, allora, vultendu à a definizione di un flussu fretu, ogni pipa dumandarà dati da u servitore, chì hè stranu per dì u minimu. È se cunvertisce un flussu friddu à un caldu, allora a dumanda succederà una volta.
In generale, capisce u tipu di flussi hè abbastanza difficiule per i principianti, ma impurtante.
uperatori
return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
.pipe(
tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
map(({ data }: TradeCompanyList) => data)
);
L'operatori ci danu l'uppurtunità di travaglià cù i flussi. Aiutanu à cuntrullà l'avvenimenti chì scorri in u Observable. Avemu da cunsiderà un paru di i più populari, è più infurmazioni nantu à l'operatori ponu esse truvati in i ligami in l'infurmazioni utili.
Operatori-di
Cuminciamu cù l'operatore helper di. Crea un Observable basatu annantu à un valore simplice.
Operatori-filtru
L'operatore di filtru, cum'è u nome suggerisce, filtra u signale di u flussu. Se l'operatore torna vera, allora salta più.
Operatori - piglià
piglià - Piglia u valore di u numeru di emissioni, dopu chì u flussu finisci.
Operators-debounceTime
debounceTime - scarta i valori emessi chì cadenu in l'intervallu di tempu specificatu trà e dati di output - dopu chì l'intervallu di tempu hè passatu, emette l'ultimu valore.
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)
);
Operators-takeWhile
Emette i valori finu à chì TakeWhile torna false, dopu annulla l'abbonamentu da u flussu.
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 )
);
Operatori-combineLatest
L'operatore cumminatu combineLatest hè un pocu simili à promise.all. Unisce parechji flussi in unu. Dopu chì ogni filu hà fattu almenu una emissione, avemu l'ultimi valori da ognunu cum'è un array. In più, dopu ogni emissione da i flussi cumminati, darà novi valori.
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));
Operatori-zip
Zip - aspetta un valore da ogni flussu è forma un array basatu annantu à questi valori. Se u valore ùn vene micca da alcun filu, u gruppu ùn serà micca furmatu.
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));
Operatori - forkJoin
forkJoin unisce ancu i fili, ma solu emette un valore quandu tutti i fili sò cumpleti.
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);
Operatori-mappa
L'operatore di trasfurmazioni di mappa trasforma u valore di emissione in un novu.
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)
);
Operatori - sparte, tocca
L'operatore di tap permette di fà effetti secundari, vale à dì qualsiasi azzioni chì ùn anu micca a sequenza.
L'operatore di utilità di sparte pò trasfurmà un flussu friddu in un caldu.
L'operatori sò fatti. Passemu à u Subject.
Pensendu à voce alta
E poi andò à beie tè. Sò stancu di sti esempi 😀
Famiglia di sughjettu
A famiglia di u sughjettu hè un primu esempiu di filamenti caldi. Queste classi sò un tipu di hibridu chì agisce cum'è observable è observatore à u stessu tempu. Siccomu u sughjettu hè un flussu caldu, deve esse unsubscribed da. Se parlemu di i metudi principali, allora questi sò:
dopu - passendu novi dati à u flussu
errore - errore è terminazione di filu
cumpletu - a fine di u filu
subscribe - subscribe to a stream
unsubscribe - unsubscribe da u flussu
asObservable - trasfurmà in un observatore
toPromise - si trasforma in una prumessa
Allocate 4 5 tipi di sugetti.
Pensendu à voce alta
Aghju dettu 4 nantu à u flussu, ma resultò chì anu aghjustatu unu più. Cume si dice, vive è amparà.
Sugettu simplice new Subject()- u tipu più simplice di sugetti. Creatu senza paràmetri. Passa i valori chì sò vinuti solu dopu l'abbonamentu.
CumportamentuSuggettu new BehaviorSubject( defaultData<T> ) - in my opinion u tipu più cumuna di sugetti-s. L'input piglia u valore predeterminatu. Salvà sempre i dati di l'ultima emissione, chì hè trasmessa quandu si subscribe. Questa classa hà ancu un metudu di valore utile chì torna u valore attuale di u flussu.
ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) - Opcionalmente, pò piglià cum'è u primu argumentu a dimensione di u buffer di i valori chì guarderà in sè stessu, è a seconda volta durante a quale avemu bisognu di cambiamenti.
asyncsubject new AsyncSubject() - nunda ùn succede quandu si sottoscrive, è u valore serà restituitu solu quandu hè cumpletu. Solu l'ultimu valore di u flussu serà tornatu.
WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - A ducumentazione hè in silenziu è eiu stessu a vecu per a prima volta. Quale sà ciò ch'ellu face, scrive, aghjunghjemu.
Uff. Ebbè, avemu cunsideratu tuttu ciò chì vulia dì oghje. Spergu chì sta infurmazione hè stata utile. Pudete leghje a lista di a literatura nantu à u vostru propiu in a tabulazione Informazioni utili.