Bonjour Ă tous. Ici, Sergey Omelnitsky. Il n'y a pas si longtemps, j'ai animĂ© un stream sur la programmation rĂ©active, oĂč j'ai parlĂ© de l'asynchrone en JavaScript. Aujourd'hui, je voudrais rĂ©sumer ce matĂ©riel.

Mais avant de commencer le contenu principal, nous devons faire une introduction. Alors, commençons par les définitions : qu'est-ce qu'une pile et une file d'attente ?
Pile â c'est une collection dont les Ă©lĂ©ments sont obtenus selon le principe « dernier entrĂ©, premier sorti » LIFO.
File d'attente â c'est une collection dont les Ă©lĂ©ments sont reçus selon le principe « premier entrĂ©, premier sorti » FIFO.
D'accord, continuons.

JavaScript est un langage de programmation à thread unique. Cela signifie qu'il n'y a qu'un seul fil d'exécution et une seule pile, dans laquelle les fonctions sont placées en attente d'exécution. Par conséquent, à un moment donné, JavaScript ne peut exécuter qu'une seule opération, les autres opérations attendant leur tour dans la pile jusqu'à ce qu'elles soient appelées.
Pile d'appels â c'est une structure de donnĂ©es qui, en termes simples, enregistre des informations sur l'endroit oĂč nous en sommes dans le programme. Lorsque nous entrons dans une fonction, nous ajoutons un enregistrement Ă son sujet en haut de la pile. Lorsque nous revenons de la fonction, nous retirons l'Ă©lĂ©ment du haut de la pile et nous nous retrouvons lĂ d'oĂč nous avons appelĂ© cette fonction. C'est tout ce que fait la pile. Et maintenant, une question trĂšs intĂ©ressante. Comment l'asynchrone fonctionne-t-il alors en JavaScript ?

En rĂ©alitĂ©, en plus de la pile, il y a dans les navigateurs une file d'attente spĂ©ciale pour travailler avec ce que l'on appelle le WebAPI. Les fonctions de cette file d'attente ne s'exĂ©cutent qu'aprĂšs que la pile est complĂštement nettoyĂ©e. Ce n'est qu'aprĂšs cela qu'elles passent de la file d'attente Ă la pile pour exĂ©cution. Si la pile contient au moins un Ă©lĂ©ment, elles ne peuvent pas entrer dans la pile. C'est exactement pour cette raison que l'appel de fonctions par timeout peut souvent ne pas ĂȘtre prĂ©cis en termes de temps, car la fonction ne peut pas entrer de la file d'attente dans la pile tant qu'elle est pleine.
Considérons l'exemple suivant et analysons son « exécution » étape par étape. De plus, regardons ce qui se passe dans le systÚme.
console.log('Hi);
setTimeout(function cb1() {
console.log('cb1');
}, 5000);
console.log('Bye');
1) Rien ne se passe encore. La console du navigateur est vide, la pile d'appels est vide.

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

3) Et elle s'exécute.

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

5) Nous passons maintenant à la commande setTimeout(function cb1() {⊠}). Elle est ajoutée à la pile d'appels.

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

7) La commande setTimeout(function cb1() {⊠}) a terminé son travail 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 minuteur termine son travail et place le callback cb1 dans la file d'attente des callbacks.

12) La boucle d'événements prend la fonction cb1 de la file d'attente des callbacks et l'ajoute à 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 d'appels.

16) La fonction cb1 est supprimée de la pile d'appels.
Regardons un exemple en dynamique :

Eh bien, nous avons vu comment l'asynchronicité est implémentée en JavaScript. Maintenant, discutons 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 rĂ©alisĂ©e qu'avec des fonctions. Celles-ci peuvent ĂȘtre transmises comme n'importe quelle autre variable Ă d'autres fonctions. C'est ainsi qu'ont Ă©tĂ© créés les callbacks. Et c'est bien, amusant et joyeux, jusqu'Ă ce que cela se transforme en tristesse, mĂ©lancolie et chagrin. Pourquoi ? C'est simple:
- Avec l'augmentation de la complexitĂ© du code, le projet devient rapidement des blocs imbriquĂ©s difficiles Ă comprendre â «callback hell».
- La gestion des erreurs peut facilement ĂȘtre nĂ©gligĂ©e.
- On ne peut pas retourner des expressions avec return.
Avec l'apparition de Promise, la situation est devenue un peu meilleure.
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 2000);
}).then((result) => {
alert(result);
return result + 2;
}).then((result) => {
throw new Error('ĂCHEC ICI');
alert(result);
return result + 2;
}).then((result) => {
alert(result);
return result + 2;
}).catch((e) => {
console.log('erreur: ', e);
});- Des chaßnes de promesses sont apparues, ce qui a amélioré la lisibilité du code.
- Un méthode distincte de gestion des erreurs est apparue.
- Il est désormais possible d'exécuter des tùches en parallÚle avec Promise.all
- Nous pouvons résoudre l'asynchronicité imbriquée avec async/await
Cependant, il y a des limitations avec les promesses. Par exemple, une promesse ne peut pas ĂȘtre annulĂ©e sans une manipulation compliquĂ©e, et surtout, elle ne fonctionne qu'avec une seule valeur.
Eh bien, nous avons tranquillement abordé la programmation réactive. Fatigué ? Heureusement, vous pouvez aller préparer une tasse de thé, réfléchir et revenir lire la suite. Pendant ce temps, je vais continuer.

Programmation rĂ©activeâââune paradigme de programmation axĂ©e sur les flux de donnĂ©es et la diffusion des modifications. Analysons plus en dĂ©tail 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 avons un champ de saisie. Nous crĂ©ons un tableau, et Ă chaque Ă©vĂ©nement keyup de l'input, nous allons sauvegarder cet Ă©vĂ©nement dans notre tableau. Il est Ă noter que notre tableau est triĂ© par temps, c'est-Ă -dire que l'index des Ă©vĂ©nements plus rĂ©cents est supĂ©rieur Ă l'index des Ă©vĂ©nements plus anciens. Ce tableau reprĂ©sente un modĂšle simplifiĂ© d'un flux de donnĂ©es, mais ce n'est pas encore un flux. Pour que ce tableau puisse ĂȘtre dĂ©signĂ© comme un flux, il doit ĂȘtre capable d'informer d'une maniĂšre ou d'une autre les abonnĂ©s qu'il a reçu de nouvelles donnĂ©es. Ainsi, nous sommes parvenus Ă la dĂ©finition d'un flux.
Flux de données
const { interval1 } = Rx;
const { take } = RxOperators;
interval(1000).pipe(
take(4)
)
Fluxââââest un tableau de donnĂ©es, triĂ© par temps, qui peut signaler que les donnĂ©es ont changĂ©. Imaginez Ă quel point il devient pratique d'Ă©crire du code qui nĂ©cessite d'appeler plusieurs Ă©vĂ©nements Ă diffĂ©rents endroits du code. Nous nous abonnissons simplement au flux, et il nous informera lui-mĂȘme des modifications. C'est ce que fait la bibliothĂšque RxJs.

RxJS âââ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 auxiliaires (Observer, Schedulers, Subjects) et des opĂ©rateurs pour travailler avec des Ă©vĂ©nements comme avec des collections (map, filter, reduce, every et similaires du tableau JavaScript).
Passons en revue les concepts de base de cette bibliothĂšque.
Observable, Observer, Producer
Observable â le premier type de base que nous allons examiner. Cette classe contient la majeure partie de l'implĂ©mentation de RxJs. Elle est liĂ©e Ă un flux observable, auquel on peut s'abonner via la mĂ©thode subscribe.
Un mĂ©canisme auxiliaire pour crĂ©er des mises Ă jour est implĂ©mentĂ© dans Observable, appelĂ© Observer. La source des valeurs pour l'Observer est appelĂ©e Producteur. Cela peut ĂȘtre un tableau, un itĂ©rateur, un web socket, un Ă©vĂ©nement, etc. On peut donc dire que l'observable agit comme un intermĂ©diaire entre le Producteur et l'Observer.
Observable gÚre trois types d'événements de l'Observer :
- next â nouvelles donnĂ©es
- error â une erreur, si la sĂ©quence se termine en raison d'une exception. Cet Ă©vĂ©nement implique aussi la fin de la sĂ©quence.
- complete â un signal indiquant la fin de la sĂ©quence. Cela signifie qu'il n'y aura plus de nouvelles donnĂ©es.
Voyons une démo :

Au début, nous traiterons les valeurs 1, 2, 3, puis aprÚs 1 seconde, nous obtiendrons 4 et terminerons notre flux.
Réflexion
Et lĂ , j'ai rĂ©alisĂ© qu'il Ă©tait plus intĂ©ressant de raconter que d'Ă©crire Ă ce sujet. đ
Subscription
Lorsque nous nous abonnons à un flux, nous créons une nouvelle classe subscription, qui nous permet d'annuler l'abonnement grùce à la méthode unsubscribe. Nous pouvons également grouper les abonnements à l'aide de la méthode add. Il est logique que nous puissions dégroupper les flux grùce à remove. Les méthodes add et remove acceptent comme argument un autre abonnement. Il est important de noter que lorsque nous nous désinscrivons, cela désinscrit également tous les abonnements enfants comme si nous avions également appelé la méthode unsubscribe sur eux. Continuons.
Types de flux
| HOT | COLD |
|---|---|
| Le Producteur est créé à l'extérieur de l'observable. | Le Producteur est créé à l'intérieur de l'observable. |
| Les données sont transmises au moment de la création de l'observable. | Les données sont communiquées au moment de l'abonnement. |
| Une logique supplĂ©mentaire est nĂ©cessaire pour la dĂ©sinscription. | Le flux se termine de lui-mĂȘme. |
| Utilise une relation un-Ă -plusieurs. | Utilise une relation un-Ă -un. |
| Tous les abonnements partagent une valeur unique. | Les abonnements sont indépendants. |
| Les donnĂ©es peuvent ĂȘtre perdues s'il n'y a pas d'abonnement. | Rediffuse toutes les valeurs du flux pour un nouvel abonnement. |
Pour faire une analogie, je reprĂ©senterais un flux chaud comme un film au cinĂ©ma. Peu importe Ă quel moment tu arrives, c'est Ă partir de ce moment que tu commences Ă regarder. Un flux froid pourrait ĂȘtre comparĂ© Ă un appel au support technique. Quiconque appelle Ă©coute l'enregistrement du rĂ©pondeur de bout en bout, mais tu peux raccrocher avec la mĂ©thode unsubscribe.
Je voudrais souligner qu'il existe Ă©galement ce qu'on appelle des flux chauds (une 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Ăč les utiliser ? Je vais donner un exemple pratique.
Je travaille avec Angular. Il utilise activement RxJS. Pour obtenir des données du serveur, j'attends un flux froid et j'utilise ce flux dans le modÚle avec asyncPipe. Si j'utilise ce pipe plusieurs fois, alors, pour revenir à la définition du flux froid, chaque pipe va demander des données au serveur, ce qui est, pour le dire poliment, étrange. Mais si je transforme un flux froid en flux chaud, la demande se fera une seule fois.
En fait, la compréhension des types de flux est assez complexe pour les débutants, mais c'est important.
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)
);Nous pouvons étendre les possibilités de travail avec les flux grùce aux opérateurs. Ils aident à contrÎler les événements qui se produisent dans un Observable. Nous allons examiner quelques-uns des plus populaires, et pour plus de détails sur les opérateurs, vous pouvez consulter les liens dans les informations utiles.
OpĂ©rateurs â of
Commençons par l'opérateur auxiliaire of. Il crée un Observable à partir d'une valeur simple.

OpĂ©rateurs â filter

L'opérateur de filtrage filter, comme son nom l'indique, filtre le signal du flux. Si l'opérateur retourne vrai, il passe à la suite.
OpĂ©rateurs â take

take â Prend le nombre d'Ă©missions aprĂšs lequel il termine le flux.
OpĂ©rateurs â debounceTime

debounceTime â rejette les valeurs Ă©mises qui se produisent dans un intervalle de temps spĂ©cifiĂ© entre les sorties â aprĂšs l'intervalle de temps Ă©coulĂ©, il Ă©met la derniĂšre valeur.
const { Observable } = Rx;
const { debounceTime, take } = RxOperators;
Observable.create((observer) => {
let i = 1;
observer.next(i++);
// Ămet une valeur toutes les 1000ms
setInterval(() => {
observer.next(i++)
}, 1000);
// Ămet une valeur toutes les 1500ms
setInterval(() => {
observer.next(i++)
}, 1500);
}).pipe(
debounceTime(700), // Attend 700ms avant de traiter la valeur
take(3)
); 
OpĂ©rateurs â takeWhile

Ămet des valeurs tant que takeWhile ne retourne pas false, aprĂšs quoi il se dĂ©sabonne du flux.
const { Observable } = Rx;
const { debounceTime, takeWhile } = RxOperators;
Observable.create((observer) => {
let i = 1;
observer.next(i++);
// Ămet une valeur toutes les 1000 ms
setInterval(() => {
observer.next(i++)
}, 1000);
}).pipe(
takeWhile(producer => producer < 5)
); 
OpĂ©rateurs â combineLatest
L'opérateur combiné combineLatest est similaire à promise.all. Il regroupe plusieurs flux en un seul. Une fois que chaque flux a émis au moins une valeur, nous recevons les derniÚres valeurs de chacun sous forme de tableau. Ensuite, aprÚs n'importe quelle émission des flux combinés, il renverra de nouvelles valeurs.

const { combineLatest, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Ămet une valeur toutes les 1000 ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
});
const observer_2 = Observable.create((observer) => {
let i = 1;
// Ămet une valeur toutes les 750 ms
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
});
combineLatest(observer_1, observer_2).pipe(take(5));
OpĂ©rateurs â zip
Zip attend une valeur de chaque flux et forme un tableau basé sur ces valeurs. Si aucune valeur n'est reçue d'un flux, le groupe ne sera pas formé.

const { zip, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Ămet une valeur toutes les 1000 ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
});
const observer_2 = Observable.create((observer) => {
let i = 1;
// Ămet une valeur toutes les 750 ms
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
});
const observer_3 = Observable.create((observer) => {
let i = 1;
// Ămet une valeur toutes les 500 ms
setInterval(() => {
observer.next('c: ' + i++);
}, 500);
});
zip(observer_1, observer_2, observer_3).pipe(take(5));
OpĂ©rateurs â forkJoin
forkJoin regroupe également les flux, mais il émet une valeur uniquement lorsque tous les flux sont terminés (complétés).

const { forkJoin, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Ămet une valeur toutes les 1000 ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
}).pipe(take(3));
const observer_2 = Observable.create((observer) => {
let i = 1;
// Ămet une valeur toutes les 750 ms
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
}).pipe(take(5));
const observer_3 = Observable.create((observer) => {
let i = 1;
// Ămet une valeur toutes les 500 ms
setInterval(() => {
observer.next('c: ' + i++);
}, 500);
}).pipe(take(4));
forkJoin(observer_1, observer_2, observer_3);
OpĂ©rateurs â map
L'opérateur de transformation map convertit la valeur émise en une nouvelle.

const { Observable } = Rx;
const { take, map } = RxOperators;
Observable.create((observer) => {
let i = 1;
// Ămet une valeur toutes les 1000 ms
setInterval(() => {
observer.next(i++);
}, 1000);
}).pipe(
map(x => x * 10),
take(3)
);

OpĂ©rateurs â share, tap
L'opérateur tap permet d'exécuter des effets secondaires, c'est-à -dire toute action qui n'affecte pas la séquence.
L'opérateur utilitaire share peut transformer un flux froid en un flux chaud.

Nous avons terminé avec les opérateurs. Passons au Subject.
Réflexion
Et lĂ , je suis allĂ© prendre un thĂ©. Ces exemples m'ont fatiguĂ© đ
La famille des subjects
La famille des subjects est un exemple frappant de flux chauds. Ces classes sont en quelque sorte des hybrides, agissant Ă la fois comme observable et observer. Ătant donnĂ© que le subject est un flux chaud, il est nĂ©cessaire de s'y dĂ©sabonner. En ce qui concerne les principales mĂ©thodes, les voici :
- next â envoi de nouvelles donnĂ©es dans le flux
- error â erreur et terminaison du flux
- complete â terminaison du flux
- subscribe â s'abonner au flux
- unsubscribe â se dĂ©sabonner du flux
- asObservable â transformĂ© en observateur
- toPromise â transformĂ© en promesse
On distingue 4 ou 5 types de subjects.
Réflexion
J'ai parlé de 4 lors du stream, mais apparemment ils en ont ajouté un de plus. Comme on dit, on vit et on apprend.
Subject simple new Subject()â le type de subject le plus simple. Créé sans paramĂštres. Transmet les valeurs reçues uniquement aprĂšs l'abonnement.
BehaviorSubject new BehaviorSubject(defaultData) â Ă mon avis, le type de subject le plus couramment utilisĂ©. Prend en entrĂ©e une valeur par dĂ©faut. Toujours conserve les donnĂ©es de la derniĂšre Ă©mission, qui sont transmises lors de l'abonnement. Cette classe possĂšde Ă©galement la mĂ©thode utile value, qui retourne la valeur actuelle du flux.
ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) â En entrĂ©e, il peut optionnellement prendre comme premier argument la taille du buffer des valeurs qu'il va stocker, et comme deuxiĂšme le temps pendant lequel nous avons besoin des modifications.
AsyncSubject new AsyncSubject() â lors de l'abonnement, rien ne se passe, et la valeur sera retournĂ©e uniquement lors de complete. Seule la derniĂšre valeur du flux sera retournĂ©e.
WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig | Observable, destination?: Observer) â La documentation est silencieuse Ă son sujet et je le vois pour la premiĂšre fois. Qui sait ce qu'il fait, Ă©crivez, nous complĂ©terons.
Ouf. Eh bien, nous avons couvert tout ce que je voulais dire aujourd'hui. J'espÚre que cette information a été utile. Vous pouvez consulter la bibliographie dans l'onglet informations utiles.
Informations utiles
- â lien vers le stream
- â Comment fonctionne JS : aperçu du moteur, des mĂ©canismes d'exĂ©cution, de la pile d'appels
- â Comment fonctionne JS : boucle d'Ă©vĂ©nements, asynchronisme et cinq façons d'amĂ©liorer le code avec async / await
- â Comment fonctionne la boucle d'Ă©vĂ©nements en JavaScript
- â L'Ă©volution de JavaScript asynchrone
- â Qu'est-ce que RxJS et pourquoi est-il utile de le connaĂźtre
- â Applications pratiques de RxJS
- â Tutoriel RxJS Observables â CrĂ©ation et abonnement aux Observables
- â RXJS : Observables chauds et froids
- â Classes, fonctions pour crĂ©er des Observable. OpĂ©rateurs.
- â OpĂ©rateurs RxJS par exemple
- â Liste API
- â Types de Subject et Scheduling dans RxJS
Source : habr.com
