Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Salut tout le monde. En contact Omelnitsky Sergey. Il n'y a pas si longtemps, j'animais un stream sur la programmation réactive, où je parlais de l'asynchronisme en JavaScript. Aujourd'hui, je voudrais résumer ce matériel.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Mais avant de commencer le matériel principal, nous devons faire une introduction. Commençons donc par les définitions : qu'est-ce que la pile et la file d'attente ?

Pile est une collection dont les éléments sont récupérés sur une base LIFO « dernier entré, premier sorti »

File d'attente est une collection dont les éléments sont obtenus selon le principe « premier entré, premier sorti » FIFO

Bon, continuons.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

JavaScript est un langage de programmation monothread. Cela signifie qu'il n'a qu'un seul thread d'exécution et une seule pile où les fonctions sont mises en file d'attente pour exécution. Par conséquent, JavaScript ne peut effectuer qu'une seule opération à la fois, tandis que les autres opérations attendront leur tour sur la pile jusqu'à ce qu'elles soient appelées.

Pile d'appels est une structure de données qui, en termes simples, enregistre des informations sur l'endroit du programme où nous nous trouvons. Si nous sautons dans une fonction, nous poussons son entrée en haut de la pile. Lorsque nous revenons d'une fonction, nous extrayons l'élément le plus haut de la pile et nous retrouvons là où nous avons appelé cette fonction. C'est tout ce que la pile peut faire. Et maintenant une question très intéressante. Comment fonctionne alors l'asynchronisme en JavaScript ?

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

En fait, en plus de la pile, les navigateurs ont une file d'attente spéciale pour travailler avec la soi-disant WebAPI. Les fonctions de cette file d'attente ne seront exécutées dans l'ordre qu'une fois la pile complètement effacée. Ce n'est qu'après cela qu'ils sont placés de la file d'attente sur la pile pour exécution. S'il y a au moins un élément sur la pile en ce moment, il ne peut pas monter sur la pile. Juste à cause de cela, l'appel de fonctions par timeout est souvent imprécis dans le temps, car la fonction ne peut pas passer de la file d'attente à la pile tant qu'elle est pleine.

Jetons un coup d'œil à l'exemple suivant et passons en revue étape par étape. Voyons aussi ce qui se passe dans le système.

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

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

1) Pour l'instant rien ne se passe. La console du navigateur est propre, la pile des appels est vide.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

2) Ensuite, la commande console.log('Hi') est ajoutée à la pile des appels.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

3) Et c'est accompli

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

4) Ensuite, console.log('Hi') est supprimé de la pile des appels.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

5) Passons maintenant à la commande setTimeout(function cb1() {… }). Il est ajouté à la pile des appels.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

6) La commande setTimeout(function cb1() {… }) est exécutée. Le navigateur crée un minuteur qui fait partie de l'API Web. Il effectuera un compte à rebours.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

7) La commande setTimeout(function cb1() {… }) a terminé son travail et est supprimée de la pile des appels.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

8) La commande console.log('Bye') est ajoutée à la pile des appels.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

9) La commande console.log('Bye') est exécutée.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

10) La commande console.log('Bye') est supprimée de la pile des appels.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

11) Après qu'au moins 5000 ms se soient écoulés, le temporisateur se termine et place le rappel cb1 dans la file d'attente de rappel.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

12) La boucle d'événements prend la fonction cb1 de la file d'attente de rappel et la pousse sur la pile des appels.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

13) La fonction cb1 est exécutée et ajoute console.log('cb1') à la pile des appels.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

14) La commande console.log('cb1') est exécutée.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

15) La commande console.log('cb1') est supprimée de la pile des appels.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

16) La fonction cb1 est supprimée de la pile des appels.

Prenons un exemple en dynamique :

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Eh bien, nous avons examiné comment l'asynchronisme est implémenté en JavaScript. Parlons maintenant brièvement de l'évolution du code asynchrone.

L'évolution du code asynchrone.

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 programmation asynchrone telle que nous la connaissons en JavaScript ne peut se faire qu'avec des fonctions. Elles peuvent être passées comme n'importe quelle autre variable à d'autres fonctions. C'est ainsi que sont nés les rappels. Et c'est cool, amusant et fervent, jusqu'à ce que ça se transforme en tristesse, mélancolie et tristesse. Pourquoi? Oui, c'est simple :

  • Au fur et à mesure que la complexité du code augmente, le projet se transforme rapidement en plusieurs blocs imbriqués obscurs - "l'enfer des rappels".
  • La gestion des erreurs peut être facilement négligée.
  • Vous ne pouvez pas renvoyer d'expressions avec return.

Avec l'avènement de Promise, la situation s'est un peu améliorée.

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

  • Des chaînes de promesses sont apparues, ce qui a amélioré la lisibilité du code
  • Il y avait une méthode distincte d'interception des erreurs
  • Exécution parallèle avec Promise.all ajouté
  • Nous pouvons résoudre l'asynchronisme imbriqué avec async/wait

Mais la promesse a ses limites. Par exemple, une promesse, sans danser avec un tambourin, ne peut pas être annulée et, surtout, elle fonctionne avec une valeur.

Eh bien, nous abordons en douceur la programmation réactive. Fatigué? Eh bien, la bonne chose est que vous pouvez aller brasser des goélands, réfléchir et revenir pour en savoir plus. Et je continuerai.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Programmation réactive - un paradigme de programmation centré sur les flux de données et la propagation des changements. Examinons de plus près ce qu'est un flux de données.

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

const eventsArray = [];

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

Imaginons que nous ayons un champ de saisie. Nous créons un tableau, et pour chaque keyup de l'événement d'entrée, nous stockerons l'événement dans notre tableau. En même temps, je voudrais noter que notre tableau est trié par heure, c'est-à-dire l'indice des événements ultérieurs est supérieur à l'indice des événements antérieurs. Un tel tableau est un modèle de flux de données simplifié, mais ce n'est pas encore un flux. Pour que ce tableau soit appelé un flux en toute sécurité, il doit être capable d'informer d'une manière ou d'une autre les abonnés que de nouvelles données y sont arrivées. Nous arrivons ainsi à la définition du flux.

Flux de données

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

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

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Écoulement est un tableau de données triées par heure qui peut indiquer que les données ont changé. Imaginez maintenant à quel point il devient pratique d'écrire du code dans lequel vous devez déclencher plusieurs événements dans différentes parties du code pour une action. Nous nous abonnons simplement au flux et il nous dira quand des changements se produiront. Et la bibliothèque RxJs peut le faire.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

RxJS est une bibliothèque pour travailler avec des programmes asynchrones et basés sur des événements utilisant des séquences observables. La bibliothèque fournit le type principal observable, plusieurs types d'assistants (Observateurs, planificateurs, sujets) et des opérateurs pour travailler avec des événements comme avec des collections (mapper, filtrer, réduire, chaque et similaires de JavaScript Array).

Comprenons les concepts de base de cette bibliothèque.

Observable, Observateur, Producteur

Observable est le premier type de base que nous allons examiner. Cette classe contient la partie principale de l'implémentation de RxJs. Il est associé à un flux observable, auquel il est possible de s'abonner à l'aide de la méthode d'abonnement.

Observable implémente un mécanisme auxiliaire pour créer des mises à jour, le soi-disant Observateur. La source des valeurs d'un Observateur s'appelle Nom de domaine. Il peut s'agir d'un tableau, d'un itérateur, d'un socket Web, d'un type d'événement, etc. On peut donc dire que l'observable est un conducteur entre Producteur et Observateur.

Observable gère trois types d'événements Observer :

  • suivant - nouvelles données
  • erreur - une erreur si la séquence s'est terminée en raison d'une exception. cet événement implique également la fin de la séquence.
  • complete - un signal sur la fin de la séquence. Cela signifie qu'il n'y aura plus de nouvelles données

Voyons une démo :

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Au début, nous traiterons les valeurs 1, 2, 3 et après 1 sec. nous obtenons 4 et terminons notre fil.

Penser a voix haute

Et puis j'ai compris que c'était plus intéressant à raconter qu'à écrire. 😀

Abonnements

Lorsque nous nous abonnons à un flux, nous créons une nouvelle classe abonnement, ce qui nous donne la possibilité de se désabonner avec la méthode vous désabonner. Nous pouvons également regrouper les abonnements en utilisant la méthode ajouter. Eh bien, il est logique que nous puissions dissocier les threads en utilisant supprimez. Les méthodes d'ajout et de suppression acceptent un abonnement différent en entrée. Je voudrais noter que lorsque nous nous désinscrivons, nous nous désinscrivons de tous les abonnements enfants comme s'ils appelaient également la méthode de désabonnement. Poursuivre.

Types de flux

HOT
DU FROID

Le producteur est créé en dehors de l'observable
Le producteur est créé à l'intérieur de l'observable

Les données sont transmises au moment où l'observable est créé
Les données sont fournies au moment de la souscription.

Besoin de plus de logique pour se désabonner
Le fil se termine tout seul

Utilise une relation un-à-plusieurs
Utilise une relation un à un

Tous les abonnements ont la même valeur
Les abonnements sont indépendants

Les données peuvent être perdues s'il n'y a pas d'abonnement
Réémet toutes les valeurs de flux pour un nouvel abonnement

Pour donner une analogie, j'imaginerais un flux chaud comme un film dans un cinéma. À quel moment vous êtes venu, à partir de ce moment vous avez commencé à regarder. Je comparerais un flux froid avec un appel dans ceux-ci. soutien. Tout appelant écoute l'enregistrement du répondeur du début à la fin, mais vous pouvez raccrocher en vous désinscrivant.

Je voudrais noter qu'il existe également des flux dits chauds (j'ai rencontré une telle définition extrêmement rarement et uniquement dans des communautés étrangères) - il s'agit d'un flux qui se transforme d'un flux froid en un flux chaud. La question se pose - où utiliser)) Je vais donner un exemple tiré de la pratique.

Je travaille avec Angular. Il utilise activement rxjs. Pour obtenir des données sur le serveur, j'attends un flux froid et j'utilise ce flux dans le modèle en utilisant asyncPipe. Si j'utilise plusieurs fois ce tuyau, alors, revenant à la définition d'un flux froid, chaque tuyau demandera des données au serveur, ce qui est pour le moins étrange. Et si je convertis un flux froid en flux chaud, la demande se produira une fois.

En général, comprendre le type de flux est assez difficile pour les débutants, mais important.

Les opérateurs

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

Les opérateurs nous offrent la possibilité de travailler avec des flux. Ils aident à contrôler les événements circulant dans l'Observable. Nous examinerons quelques-uns des plus populaires, et plus d'informations sur les opérateurs peuvent être trouvées sur les liens dans des informations utiles.

Opérateurs de

Commençons par l'opérateur d'assistance de. Il crée un Observable basé sur une valeur simple.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Opérateurs-filtre

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

L'opérateur de filtrage, comme son nom l'indique, filtre le signal du flux. Si l'opérateur renvoie true, il saute plus loin.

Opérateurs - prendre

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

take - Prend la valeur du nombre d'émissions, après quoi le flux se termine.

Operators-debounceTime

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

debounceTime - supprime les valeurs émises qui se situent dans l'intervalle de temps spécifié entre les données de sortie - une fois l'intervalle de temps écoulé, émet la dernière valeur.

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

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Opérateurs-takeWhile

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Émet des valeurs jusqu'à ce que takeWhile renvoie false, puis se désabonne du flux.

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

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Operators-combineLatest

L'opérateur combiné combineLatest est quelque peu similaire à promise.all. Il combine plusieurs flux en un seul. Une fois que chaque thread a effectué au moins une émission, nous obtenons les dernières valeurs de chacun sous forme de tableau. De plus, après toute émission des flux combinés, cela donnera de nouvelles valeurs.

Programmation asynchrone en 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));

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Opérateurs-zip

Zip - attend une valeur de chaque flux et forme un tableau basé sur ces valeurs. Si la valeur ne provient d'aucun thread, le groupe ne sera pas formé.

Programmation asynchrone en 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));

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Opérateurs - forkJoin

forkJoin joint également les threads, mais il n'émet une valeur que lorsque tous les threads sont terminés.

Programmation asynchrone en 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);

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Carte des opérateurs

L'opérateur de transformation de carte transforme la valeur d'émission en une nouvelle.

Programmation asynchrone en 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)
);

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Opérateurs - partagez, touchez

L'opérateur du robinet vous permet de faire des effets secondaires, c'est-à-dire toutes les actions qui n'affectent pas la séquence.

L'opérateur de service public partagé peut transformer un flux froid en flux chaud.

Programmation asynchrone en JavaScript (Callback, Promise, RxJs )

Les opérateurs sont terminés. Passons au Sujet.

Penser a voix haute

Et puis je suis allé boire du thé. J'en ai marre de ces exemples 😀

Famille de sujets

La famille de sujets est un excellent exemple de fils chauds. Ces classes sont une sorte d'hybride qui agit à la fois comme observable et observateur. Étant donné que le sujet est un flux chaud, il doit être désabonné. Si nous parlons des principales méthodes, alors ce sont:

  • suivant - transmettre de nouvelles données au flux
  • erreur - erreur et fin de thread
  • complet - fin du fil
  • s'abonner - s'abonner à un flux
  • se désabonner - se désabonner du fil de discussion
  • asObservable - transformer en observateur
  • toPromise - se transforme en promesse

Allouer 4 5 types de sujets.

Penser a voix haute

J'ai dit 4 sur le stream, mais il s'est avéré qu'ils en ont ajouté un de plus. Comme dit le proverbe, vivre et apprendre.

Sujet simple new Subject()- le type de sujets le plus simple. Créé sans paramètres. Passe les valeurs qui ne sont venues qu'après l'abonnement.

ComportementSujet new BehaviorSubject( defaultData<T> ) - à mon avis, le type le plus courant de sujet-s. L'entrée prend la valeur par défaut. Enregistre toujours les données du dernier numéro, qui sont transmises lors de l'abonnement. Cette classe a également une méthode de valeur utile qui renvoie la valeur actuelle du flux.

ReplaySujet new ReplaySubject(bufferSize?: number, windowTime?: number) - Eventuellement, il peut prendre comme premier argument la taille du buffer de valeurs qu'il va stocker en lui-même, et le second temps pendant lequel on a besoin de changements.

sujet asynchrone new AsyncSubject() - rien ne se passe lors de l'abonnement et la valeur ne sera renvoyée qu'une fois terminée. Seule la dernière valeur du flux sera renvoyée.

WebSocketSujet new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - La documentation est muette à ce sujet et je le vois moi-même pour la première fois. Qui sait ce qu'il fait, écrire, ajouterons-nous.

Phew. Eh bien, nous avons considéré tout ce que je voulais dire aujourd'hui. J'espère que cette information a été utile. Vous pouvez lire la liste de la littérature par vous-même dans l'onglet Informations utiles.

informations utiles

Source: habr.com

Ajouter un commentaire