Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Ciao a tutti. In contatto Omelnitsky Sergey. Non molto tempo fa ho ospitato uno streaming sulla programmazione reattiva, in cui ho parlato dell'asincronia in JavaScript. Oggi vorrei riassumere questo materiale.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Ma prima di iniziare la trattazione principale, occorre fare una premessa. Partiamo quindi dalle definizioni: cosa sono stack e coda?

Pila è una raccolta i cui elementi vengono recuperati su base LIFO “last in, first out”.

Turno è una raccolta i cui elementi sono ottenuti secondo il principio (“first in, first out” FIFO

Ok, continuiamo.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

JavaScript è un linguaggio di programmazione a thread singolo. Ciò significa che ha un solo thread di esecuzione e uno stack in cui le funzioni vengono accodate per l'esecuzione. Pertanto, JavaScript può eseguire solo un'operazione alla volta, mentre le altre operazioni attenderanno il loro turno nello stack finché non verranno chiamate.

stack di chiamate è una struttura dati che, in termini semplici, registra informazioni sul punto del programma in cui ci troviamo. Se entriamo in una funzione, spostiamo la sua voce in cima allo stack. Quando torniamo da una funzione, estraiamo l'elemento più in alto dallo stack e finiamo nel punto da cui abbiamo chiamato questa funzione. Questo è tutto ciò che lo stack può fare. E ora una domanda molto interessante. Come funziona allora l'asincronia in JavasScript?

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Infatti, oltre allo stack, i browser hanno una coda speciale per lavorare con la cosiddetta WebAPI. Le funzioni di questa coda verranno eseguite in ordine solo dopo che lo stack sarà stato completamente cancellato. Solo dopo vengono posizionati dalla coda nello stack per l'esecuzione. Se al momento c'è almeno un elemento in pila, allora non può entrare in pila. Proprio per questo motivo, chiamare le funzioni per timeout è spesso impreciso nel tempo, poiché la funzione non può passare dalla coda allo stack mentre è pieno.

Diamo un'occhiata al seguente esempio e analizziamolo passo dopo passo. Vediamo anche cosa succede nel sistema.

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

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

1) Per ora non è successo nulla. La console del browser è pulita, lo stack di chiamate è vuoto.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

2) Quindi il comando console.log('Hi') viene aggiunto allo stack di chiamate.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

3) E si è avverato

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

4) Quindi console.log('Hi') viene rimosso dallo stack di chiamate.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

5) Passiamo ora al comando setTimeout(function cb1() {… }). Viene aggiunto allo stack di chiamate.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

6) Viene eseguito il comando setTimeout(function cb1() {… }). Il browser crea un timer che fa parte dell'API Web. Eseguirà un conto alla rovescia.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

7) Il comando setTimeout(function cb1() {… }) ha completato il suo lavoro e viene rimosso dallo stack di chiamate.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

8) Il comando console.log('Bye') viene aggiunto allo stack di chiamate.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

9) Viene eseguito il comando console.log('Bye').

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

10) Il comando console.log('Bye') viene rimosso dallo stack di chiamate.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

11) Dopo che sono trascorsi almeno 5000 ms, il timer termina e inserisce la callback cb1 nella coda di callback.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

12) Il loop degli eventi prende la funzione cb1 dalla coda di callback e la inserisce nello stack di chiamate.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

13) La funzione cb1 viene eseguita e aggiunge console.log('cb1') allo stack di chiamate.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

14) Viene eseguito il comando console.log('cb1').

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

15) Il comando console.log('cb1') viene rimosso dallo stack di chiamate.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

16) La funzione cb1 viene rimossa dallo stack di chiamate.

Diamo un'occhiata ad un esempio in dinamica:

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Bene, abbiamo esaminato come viene implementata l'asincronia in JavaScript. Parliamo ora brevemente dell'evoluzione del codice asincrono.

L'evoluzione del codice asincrono.

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

La programmazione asincrona come la conosciamo in JavaScript può essere eseguita solo con funzioni. Possono essere passati come qualsiasi altra variabile ad altre funzioni. Ecco come sono nati i callback. Ed è bello, divertente e fervente, finché non si trasforma in tristezza, malinconia e tristezza. Perché? Sì, è semplice:

  • Man mano che la complessità del codice cresce, il progetto si trasforma rapidamente in oscuri blocchi multipli nidificati: "l'inferno dei callback".
  • La gestione degli errori può essere facilmente trascurata.
  • Non è possibile restituire espressioni con return.

Con l'avvento di Promise la situazione è leggermente migliorata.

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

  • Sono apparse catene di promesse, che hanno migliorato la leggibilità del codice
  • Esisteva un metodo separato di intercettazione degli errori
  • Aggiunta esecuzione parallela con Promise.all
  • Possiamo risolvere l'asincronia annidata con async/await

Ma le promesse hanno i loro limiti. Ad esempio, una promessa non può essere annullata senza ballare con un tamburello, e la cosa più importante è che funzioni con un valore.

Bene, qui ci stiamo avvicinando senza intoppi alla programmazione reattiva. Stanco? Bene, la cosa buona è che puoi andare a preparare alcuni gabbiani, fare un brainstorming e tornare per leggere di più. E continuerò.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Programmazione reattiva - un paradigma di programmazione incentrato sui flussi di dati e sulla propagazione dei cambiamenti. Diamo uno sguardo più da vicino a cos'è un flusso di dati.

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

const eventsArray = [];

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

Immaginiamo di avere un campo di input. Creiamo un array e per ogni keyup dell'evento di input, memorizzeremo l'evento nel nostro array. Allo stesso tempo, vorrei notare che il nostro array è ordinato in base al tempo, ad es. l'indice degli eventi successivi è maggiore dell'indice degli eventi precedenti. Tale array è un modello di flusso di dati semplificato, ma non è ancora un flusso. Affinché questo array possa essere chiamato in modo sicuro un flusso, deve essere in grado di informare in qualche modo gli abbonati che sono arrivati ​​​​nuovi dati. Arriviamo così alla definizione di flusso.

Flusso di dati

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

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

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

ruscello è un array di dati ordinati per ora che può indicare che i dati sono cambiati. Ora immagina quanto diventa conveniente scrivere codice in cui è necessario attivare più eventi in diverse parti del codice per un'azione. Ci iscriviamo semplicemente allo streaming e ci dirà quando si verificano i cambiamenti. E la libreria RxJs può farlo.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

RxJS è una libreria per lavorare con programmi asincroni e basati su eventi utilizzando sequenze osservabili. La libreria fornisce il tipo principale Osservabile, diversi tipi di helper (Osservatore, pianificatori, soggetti) e operatori per lavorare con gli eventi come con le collezioni (mappare, filtrare, ridurre, ogni e simili da JavaScript Array).

Comprendiamo i concetti di base di questa libreria.

Osservabile, Osservatore, Produttore

Osservabile è il primo tipo di base che esamineremo. Questa classe contiene la parte principale dell'implementazione RxJs. È associato a un flusso osservabile, a cui è possibile iscriversi utilizzando il metodo di iscrizione.

Observable implementa un meccanismo ausiliario per la creazione di aggiornamenti, il cosiddetto Osservatore. Viene chiamata la fonte dei valori per un osservatore Produttore. Potrebbe trattarsi di un array, un iteratore, un socket web, qualche tipo di evento, ecc. Quindi possiamo dire che l'osservabile è un filo conduttore tra Produttore e Osservatore.

Observable gestisce tre tipi di eventi Observer:

  • successivo – nuovi dati
  • error - Un errore se la sequenza è terminata a causa di un'eccezione. questo evento implica anche la fine della sequenza.
  • completo: un segnale sulla fine della sequenza. Ciò significa che non ci saranno più nuovi dati

Vediamo la demo:

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

All'inizio elaboreremo i valori 1, 2, 3 e dopo 1 sec. otteniamo 4 e terminiamo il nostro thread.

Pensare ad alta voce

E poi ho capito che era più interessante raccontarlo che scriverlo. 😀

Sottoscrizione

Quando ci iscriviamo a uno stream, creiamo una nuova classe sottoscrizione, che ci dà la possibilità di annullare l'iscrizione con il metodo cancellarsi. Possiamo anche raggruppare gli abbonamenti utilizzando il metodo aggiungere. Bene, è logico che possiamo separare i thread usando rimuovere. I metodi di aggiunta e rimozione accettano una sottoscrizione diversa come input. Vorrei sottolineare che quando annulliamo l'iscrizione, annulliamo l'iscrizione a tutti gli abbonamenti secondari come se chiamassero anche il metodo di annullamento dell'iscrizione. Andare avanti.

Tipi di flussi

HOT
FREDDO

Il produttore viene creato al di fuori dell'osservabile
Il produttore viene creato all'interno dell'osservabile

I dati vengono passati al momento della creazione dell'osservabile
I dati vengono forniti al momento della sottoscrizione.

Serve più logica per annullare l'iscrizione
Il thread termina da solo

Utilizza una relazione uno-a-molti
Utilizza una relazione uno a uno

Tutti gli abbonamenti hanno lo stesso valore
Gli abbonamenti sono indipendenti

I dati possono andare persi se non è presente alcun abbonamento
Riemette tutti i valori del flusso per un nuovo abbonamento

Per fare un'analogia, immaginerei un flusso caldo come un film al cinema. A che punto sei arrivato, da quel momento hai iniziato a guardare. Paragonerei un flusso freddo con una chiamata in quelli. supporto. Qualsiasi chiamante ascolta la registrazione della segreteria telefonica dall'inizio alla fine, ma è possibile riattaccare annullando l'iscrizione.

Vorrei sottolineare che esistono anche i cosiddetti flussi caldi (ho incontrato una definizione del genere estremamente raramente e solo in comunità straniere): questo è un flusso che si trasforma da flusso freddo a caldo. Sorge la domanda: dove usarlo)) Darò un esempio tratto dalla pratica.

Sto lavorando con Angular. Utilizza attivamente rxjs. Per inviare i dati al server, mi aspetto un flusso freddo e utilizzo questo flusso nel modello utilizzando asyncPipe. Se utilizzo questa pipe più volte, tornando alla definizione di flusso freddo, ogni pipe richiederà dati al server, il che è a dir poco strano. E se converto un flusso freddo in uno caldo, la richiesta avverrà una volta.

In generale, comprendere la tipologia dei flussi è abbastanza difficile per i principianti, ma è importante.

Operatori

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

Gli operatori ci offrono l'opportunità di lavorare con i flussi. Aiutano a controllare gli eventi che scorrono nell'Osservabile. Considereremo un paio di quelli più popolari e maggiori informazioni sugli operatori possono essere trovate nei collegamenti in informazioni utili.

Operatori - di

Cominciamo con l'operatore ausiliario di. Crea un osservabile basato su un valore semplice.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Filtro operatori

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

L'operatore filtro, come suggerisce il nome, filtra il segnale del flusso. Se l'operatore restituisce true, salta ulteriormente.

Operatori: prendi

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

take - Prende il valore del numero di emissioni, dopo il quale lo stream termina.

Operatori-debounceTime

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

debounceTime - scarta i valori emessi che rientrano nell'intervallo di tempo specificato tra i dati di output - dopo che è trascorso l'intervallo di tempo, emette l'ultimo 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)
);  

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Operatori-takeWhile

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Emette valori finché takeWhile restituisce false, dopodiché annulla l'iscrizione al 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 )
);  

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Operatori-combineLatest

L'operatore combinato combineLatest è in qualche modo simile a promise.all. Combina più flussi in uno solo. Dopo che ogni thread ha effettuato almeno un'emissione, otteniamo i valori più recenti da ciascuno come array. Inoltre, dopo ogni emissione dai flussi combinati, fornirà nuovi valori.

Programmazione asincrona in 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));

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Operatori-zip

Zip: attende un valore da ciascun flusso e forma un array basato su questi valori. Se il valore non proviene da nessun thread, il gruppo non verrà formato.

Programmazione asincrona in 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));

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Operatori - forkJoin

Anche forkJoin unisce i thread, ma emette un valore solo quando tutti i thread sono completi.

Programmazione asincrona in 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);

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Mappa degli operatori

L'operatore di trasformazione della mappa trasforma il valore di emissione in uno nuovo.

Programmazione asincrona in 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)
);

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Operatori: condividi, tocca

L'operatore tap ti consente di eseguire effetti collaterali, ovvero qualsiasi azione che non influisce sulla sequenza.

L'operatore del servizio di condivisione può trasformare un flusso freddo in uno caldo.

Programmazione asincrona in JavaScript (Callback, Promise, RxJs)

Gli operatori hanno finito. Passiamo all'Oggetto.

Pensare ad alta voce

E poi sono andato a bere il tè. Sono stanca di questi esempi 😀

Famiglia soggetto

La famiglia di argomenti è un ottimo esempio di hot thread. Queste classi sono una sorta di ibrido che agisce come osservabile e osservatore allo stesso tempo. Poiché l'argomento è un flusso caldo, è necessario annullare l'iscrizione. Se parliamo dei metodi principali, questi sono:

  • successivo: passaggio di nuovi dati allo stream
  • error: errore e chiusura del thread
  • completo: completamento del thread
  • iscriviti: iscriviti a uno streaming
  • annullare l'iscrizione: annullare l'iscrizione al thread
  • asObservable: trasformarsi in un osservatore
  • toPromise: si trasforma in una promessa

Assegna 4 5 tipi di argomenti.

Pensare ad alta voce

Ho detto 4 nello streaming, ma si è scoperto che ne hanno aggiunto un altro. Come dice il proverbio, vivi e impara.

Oggetto semplice new Subject()- il tipo più semplice di argomenti. Creato senza parametri. Passa i valori che sono arrivati ​​solo dopo la sottoscrizione.

ComportamentoSoggetto new BehaviorSubject( defaultData<T> ) - secondo me il tipo di soggetto più comune. L'input assume il valore predefinito. Salva sempre i dati dell'ultimo numero, che vengono trasmessi al momento dell'iscrizione. Questa classe ha anche un metodo di valore utile che restituisce il valore corrente del flusso.

Riproduci soggetto new ReplaySubject(bufferSize?: number, windowTime?: number) - Facoltativamente, può prendere come primo argomento la dimensione del buffer di valori che memorizzerà in sé e il secondo tempo durante il quale sono necessarie modifiche.

Oggetto asincrono new AsyncSubject() - al momento della sottoscrizione non succede nulla e il valore verrà restituito solo al termine. Verrà restituito solo l'ultimo valore del flusso.

OggettoWebSocket new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - La documentazione non ne parla e io stesso lo vedo per la prima volta. Chissà cosa fa, scriva, aggiungeremo noi.

Uff. Bene, abbiamo considerato tutto ciò che volevo raccontare oggi. Spero che questa informazione sia stata utile. Puoi leggere tu stesso l'elenco della letteratura nella scheda Informazioni utili.

informazioni utili

Fonte: habr.com

Aggiungi un commento