Bonjour à tous. En contact avec Omelnitsky Sergey. Il n'y a pas si longtemps, j'ai dirigé un flux de programmation réactif où j'ai parlé d'asynchronie en JavaScript. Aujourd'hui, je voudrais décrire ce matériau.

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?
Une pile est une collection dont les éléments sont reçus selon le principe du "dernier entré, premier sorti" LIFO
Une file d'attente est une collection dont les éléments sont reçus selon le principe (FIFO «premier entré, premier sorti»
Ok, continuons.

JavaScript est un langage de programmation monothread. Cela signifie qu'il n'a qu'un seul thread d'exécution et une pile dans laquelle les fonctions sont mises en file d'attente pour exécution. Par conséquent, à un moment donné, JavaScript ne peut effectuer qu'une seule opération, tandis que d'autres opérations attendent leur tour sur la pile jusqu'à ce qu'elles soient appelées.
La pile d'appels est une structure de données qui, en termes simples, enregistre des informations sur la place dans le programme où nous sommes. Si nous entrons dans une fonction, nous mettons un enregistrement à ce sujet en haut de la pile. Lorsque nous revenons de la fonction, nous tirons l'élément le plus haut de la pile et nous trouvons d'où nous avons appelé cette fonction. C'est tout ce que la pile peut faire. Et maintenant, une question extrêmement intéressante. Comment fonctionne alors l'asynchronie en JavasScript?

En fait, en plus de la pile, les navigateurs ont une file d'attente spéciale pour travailler avec ce que l'on appelle WebAPI. Les fonctions de cette file d'attente seront exécutées dans l'ordre uniquement après que la pile aura été complètement effacée. Ce n'est qu'après cela qu'ils sont poussés de la file d'attente vers la pile pour exécution. Si au moins un élément est actuellement sur la pile, alors ils ne peuvent pas entrer sur la pile. C'est précisément à cause de cela que l'appel de fonctions par timeout n'est souvent pas précis dans le temps, car une fonction ne peut pas passer de la file d'attente à la pile lorsqu'elle est pleine.
Prenons l'exemple suivant et effectuez son «exécution» étape par étape. Voir également ce qui se passe dans le système.
console.log('Hi'); setTimeout(function cb1() { console.log('cb1'); }, 5000); console.log('Bye');

1) Jusqu'à présent, rien ne se passe. La console du navigateur est propre, la pile d'appels est vide.

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

3) Et il est exécuté

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

5) Allez maintenant à la commande setTimeout (fonction cb1 () {...}). Il est ajouté à la pile d'appels.

6) La commande setTimeout (fonction cb1 () {...}) est exécutée. Le navigateur crée une minuterie qui fait partie de l'API Web. Il fera le compte à rebours.

7) La commande setTimeout (fonction cb1 () {...}) est terminée et est supprimée de la pile d'appels.

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

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

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

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

12) La boucle d'événement prend c la fonction cb1 de la file d'attente de rappel et la place sur la pile d'appels.

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

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

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

16) La fonction cb1 est supprimée de la pile d'appels.
Jetez un œil à un exemple de dynamique:

Eh bien, ici, nous avons examiné la façon dont l'asynchronie est implémentée 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 être implémentée qu'avec des fonctions. Ils peuvent être passés comme n'importe quelle autre variable à d'autres fonctions. Les rappels sont donc nés. Et c'est cool, amusant et provocant, jusqu'à ce qu'il se transforme en tristesse, envie et tristesse. Pourquoi? Oui, tout est simple:
- Avec la complexité croissante du code, le projet se transforme rapidement en blocs imbriqués à plusieurs reprises obscurs - «enfer de rappel».
- La gestion des erreurs peut être facilement manquée.
- Vous ne pouvez pas renvoyer d'expressions avec return.
Avec Promise, les choses se sont un peu améliorées.
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
- Une méthode distincte de récupération des erreurs est apparue
- Vous pouvez maintenant exécuter en parallèle en utilisant Promise.all
- Nous pouvons résoudre l'asynchronie imbriquée avec async / wait
Mais promis a ses limites. Par exemple, une promesse, sans danser avec un tambourin, ne peut pas être annulée et, surtout, elle fonctionne avec une seule valeur.
Eh bien, nous avons abordé en douceur la programmation réactive. Êtes-vous fatigué? Eh bien, la bonne chose est que vous pouvez aller faire des goélands, réfléchir et revenir pour lire plus loin. Et je vais continuer.

La programmation réactive est un paradigme de programmation axé sur les flux de données et la propagation du changement. Examinons de plus près ce qu'est un flux de données.
Imaginez que nous ayons un champ de saisie. Nous créons un tableau, et pour chaque keyup de l'événement d'entrée, nous enregistrons l'événement dans notre tableau. Dans ce cas, je voudrais noter que notre tableau est trié par le temps, 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é en toute sécurité un flux, il doit être en mesure d'informer les abonnés qu'il a reçu de nouvelles données. Nous arrivons donc à la définition du flux.
Flux de données
const { interval } = Rx; const { take } = RxOperators; interval(1000).pipe( take(4) )

Un flux 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 en une seule action. Nous venons de nous abonner au flux et il nous fera savoir quand les changements se produiront. Et la bibliothèque RxJs peut le faire.

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 d' Observable , plusieurs types auxiliaires ( Observateur, Planificateurs, Sujets ) et des opérateurs de travail avec des événements comme avec des collections ( carte, filtre, réduire, tout et similaire à partir de tableau JavaScript).
Examinons les concepts de base de cette bibliothèque.
Observable, observateur, producteur
Observable est le premier type de base que nous examinerons. Cette classe contient l'essentiel de l'implémentation RxJs. Il est associé à un flux observable auquel vous pouvez vous 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 Observer . La source de valeurs d'Observer s'appelle Producer . Il peut s'agir d'un tableau, d'un itérateur, d'une prise Web, d'une sorte d'événement, etc. On peut donc dire qu'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 l'achèvement de la séquence.
- complete - signal sur la fin de la séquence. Cela signifie qu'il n'y aura pas de nouvelles données.
Voyons la démo:

Au début, nous traiterons les valeurs 1, 2, 3 et après 1 seconde. nous en aurons 4 et terminerons notre flux.
Pensées à l'oreilleEt puis j'ai réalisé que raconter était plus intéressant que d'écrire à ce sujet. : D
Abonnement
Lorsque nous nous abonnons à un flux, nous créons une nouvelle classe d' abonnement qui nous permet de nous désinscrire en utilisant la méthode de désabonnement . Nous pouvons également regrouper les abonnements à l'aide de la méthode add . Eh bien, il est logique que nous puissions dissocier les threads avec remove . Les méthodes d'entrée ajouter et supprimer acceptent un autre abonnement. Je voudrais noter que lorsque nous nous désinscrivons, nous nous désinscrivons de tous les abonnements enfants comme s'ils appelaient la méthode de désabonnement. Allez-y.
Types de flux
Pour donner une analogie, j'imagine un flux chaud comme un film dans une salle de cinéma. À quel moment vous êtes venu, à partir de ce moment et avez commencé à regarder. Je comparerais un flux froid avec un appel dans ceux-ci. soutien. Chaque appelant écoute le répondeur du début à la fin, mais vous pouvez raccrocher en vous désinscrivant.
Je voudrais noter qu'il existe encore des flux dits chauds (une telle définition que j'ai rencontrée très rarement et uniquement dans des communautés étrangères) - c'est un flux qui se transforme d'un flux froid en un flux chaud. La question se pose - où utiliser)) Je vais donner un exemple de la pratique.
Je travaille avec un angulaire. 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 ce tube plusieurs fois, puis, revenant à la définition d'un flux froid, chaque tube 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, la compréhension de la forme des flux est assez compliquée pour les débutants, mais importante.
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 qui se produisent dans l'Observable. Nous allons considérer quelques-uns des plus populaires, et les opérateurs peuvent être trouvés plus en détail en utilisant les liens dans les informations utiles.
Opérateurs - de
Nous commençons par l'opérateur auxiliaire de. Il crée un observable basé sur une valeur simple.

Opérateurs - filtre

L'opérateur de filtre filtre, comme son nom l'indique, filtre le signal de flux. Si l'opérateur retourne vrai, il saute plus loin.
Opérateurs - prendre

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

debounceTime - supprime les valeurs émises qui tombent dans la période de temps spécifiée entre les données de sortie - après l'écoulement de l'intervalle de temps, il émet la dernière valeur.
const { Observable } = Rx; const { debounceTime, take } = RxOperators; Observable.create((observer) => { let i = 1; observer.next(i++);

Opérateurs - takeWhile

Il émet des valeurs jusqu'à ce que takeWhile renvoie false, après quoi il se désabonnera du flux.
const { Observable } = Rx; const { debounceTime, takeWhile } = RxOperators; Observable.create((observer) => { let i = 1; observer.next(i++);

Opérateurs - combineLatest
L'opérateur combine combineLatest est quelque peu similaire à promise.all. Il combine plusieurs threads en un seul. Après que chaque thread ait émis au moins une émission, nous obtenons les dernières valeurs de chacune sous la forme d'un tableau. De plus, après toute émission des flux combinés, il donnera de nouvelles valeurs.

const { combineLatest, Observable } = Rx; const { take } = RxOperators; const observer_1 = Observable.create((observer) => { let i = 1;

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 flux, le groupe ne sera pas formé.

const { zip, Observable } = Rx; const { take } = RxOperators; const observer_1 = Observable.create((observer) => { let i = 1;

Opérateurs - forkJoin
forkJoin concatène également les threads, mais il ne valorise que lorsque tous les threads sont terminés.

const { forkJoin, Observable } = Rx; const { take } = RxOperators; const observer_1 = Observable.create((observer) => { let i = 1;

Opérateurs - carte
L'opérateur de transformation de carte convertit la valeur d'émission en une nouvelle.

const { Observable } = Rx; const { take, map } = RxOperators; Observable.create((observer) => { let i = 1;

Opérateurs - partager, toucher
L'opérateur tap - vous permet de faire des effets secondaires, c'est-à-dire toutes les actions qui n'affectent pas la séquence.
L'opérateur de partage de services publics peut le faire chaud à partir d'un flux froid.

Avec les opérateurs terminés. Passons au sujet.
Pensées à l'oreilleEt puis je suis allé boire des goélands. Ces exemples m'ennuient: D
Famille du sujet
La famille de sujets est un excellent exemple de flux chauds. Ces classes sont une sorte d'hybride qui agit simultanément comme observable et observateur. Étant donné que le sujet est un flux chaud, vous devez vous en désinscrire. Si nous parlons des méthodes de base, alors ceci:
- suivant - transfert de nouvelles données vers le flux
- error - erreur et fin du flux
- complet - fin du flux
- s'abonner - s'abonner au flux
- se désinscrire - se désinscrire du flux
- asObservable - transformer en observateur
- toPromise - se transforme en promesse
Attribuer 4 5 types de sujets.
Pensées à l'oreilleIl a parlé sur le stream 4, mais il s'est avéré qu'ils en ont ajouté un de plus. Comme dit le proverbe, vivez et apprenez.
Sujet simple new Subject()
est le type de sujet le plus simple. Il est créé sans paramètres. Transmet les valeurs qui ne sont venues qu'après l'abonnement.
BehaviorSubject new BehaviorSubject( defaultData<T> )
- à mon avis, le type de sujet le plus courant. L'entrée accepte une valeur par défaut. Il enregistre toujours les données de la dernière émission, qu'il transfère lors de l'abonnement. Cette classe possède également une méthode de valeur utile qui renvoie la valeur actuelle du flux.
ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number)
- L'entrée peut éventuellement accepter le premier argument comme la taille du tampon de valeurs qu'il stockera en lui-même, et la deuxième fois pendant laquelle nous aurons besoin de modifications.
AsyncSubject new AsyncSubject()
- rien ne se produit lors de l'abonnement, et la valeur ne sera retournée qu'une fois terminée. Seule la dernière valeur de flux sera retournée.
WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>)
- La documentation est silencieuse à ce sujet et je la vois pour la première fois. Qui sait ce qu'il fait, écrit, complète.
Fuf. Eh bien, ici, nous avons considéré tout ce que je voulais dire aujourd'hui. J'espère que ces informations vous ont été utiles. Vous pouvez vous familiariser avec la liste des références dans l'onglet informations utiles.