Cet article est une compréhension personnelle de nodejs dans le développement et l'apprentissage réels. Il est maintenant compilé pour référence future. Je serais honoré s'il pouvait vous inspirer.
E/S : Entrée/Sortie, l'entrée et la sortie d'un système.
Un système peut être compris comme un individu, comme une personne. Lorsque vous parlez, c'est le résultat, et lorsque vous écoutez, c'est l'entrée.
La différence entre les E/S bloquantes et les E/S non bloquantes réside dans la capacité du système à recevoir d'autres entrées pendant la période allant de l'entrée à la sortie .
Voici deux exemples pour illustrer ce que sont les E/S bloquantes et les E/S non bloquantes :
1. Cuisson
Tout d'abord, nous devons déterminer la portée d'un système. Dans cet exemple, la tante de la cantine et le serveur du restaurant sont considérés comme un système dont l'entrée est la commande et la sortie est le service des plats .
Ensuite, si vous pouvez accepter les commandes d'autres personnes entre la commande et le service de la nourriture, vous pouvez déterminer si cela bloque les E/S ou non.
Quant à la tante de la cafétéria, elle ne peut pas commander pour les autres étudiants lors de la commande. Ce n'est qu'après que l'étudiant a fini de commander et servi les plats qu'elle peut accepter la commande de l'étudiant suivant, donc la tante de la cafétéria bloque les E/S.
Pour un serveur de restaurant, il peut servir le prochain client après la commande et avant que le client ne serve le plat, le serveur dispose donc d'E/S non bloquantes.
2. Faites le ménage
Lorsque vous lavez des vêtements, vous n'avez pas besoin d'attendre la machine à laver, vous pouvez balayer le sol et ranger le bureau à ce moment-là. Après avoir rangé le bureau, les vêtements sont lavés et vous pouvez les suspendre pour les faire sécher. 25 minutes au total.
La lessive est en fait une E/S non bloquante. Vous pouvez faire autre chose entre mettre les vêtements dans la machine à laver et terminer le lavage.
La raison pour laquelle les E/S non bloquantes peuvent améliorer les performances est qu'elles peuvent éviter des attentes inutiles.
La clé pour comprendre les E/S non bloquantes est
Comment les E/S non bloquantes de nodejs sont-elles reflétées ? Comme mentionné précédemment, un point important pour comprendre les E/S non bloquantes est de déterminer d'abord une limite système. La limite système du nœud est le thread principal .
Si le diagramme d'architecture ci-dessous est divisé en fonction de la maintenance des threads, la ligne pointillée à gauche est le thread nodejs et la ligne pointillée à droite est le thread C++.
Le thread nodejs doit maintenant interroger la base de données. Il s'agit d'une opération d'E/S typique. Il n'attendra pas les résultats de l'E/S et continuera à traiter d'autres opérations. Il distribuera une grande quantité de puissance de calcul à d'autres C++. fils pour les calculs.
Attendez que le résultat sorte et renvoyez-le au thread nodejs. Avant d'obtenir le résultat, le thread nodejs peut également effectuer d'autres opérations d'E/S, il est donc non bloquant.
Le thread nodejs est équivalent à la partie gauche étant le serveur, et le thread c++ est le chef.
Par conséquent, les E/S non bloquantes du nœud sont complétées par l’appel de threads de travail C++.
Alors, comment notifier le thread nodejs lorsque le thread c++ obtient le résultat ? La réponse est événementielle .
Blocage: le processus se met en veille pendant les E/S et attend la fin de l'E/S avant de passer à l'étape suivante ;
non bloquant : la fonction revient immédiatement pendant les E/S et le processus n'attend pas l'E/S. O pour compléter.
Alors, comment connaître le résultat renvoyé, vous devez utiliser le pilote d'événement .
Ce qu'on appelle l' événement peut être compris comme l'événement de clic frontal. J'écris d'abord un événement de clic, mais je ne sais pas quand il sera déclenché. Ce n'est que lorsqu'il sera déclenché que le thread principal le fera. exécuter la fonction événementielle.
Ce mode est également un mode observateur, c'est-à-dire que j'écoute d'abord l'événement, puis je l'exécute lorsqu'il est déclenché.
Alors, comment implémenter le lecteur d'événements ? La réponse est la programmation asynchrone .
Comme mentionné ci-dessus, nodejs dispose d'un grand nombre d'E/S non bloquantes, les résultats des E/S non bloquantes doivent donc être obtenus via des fonctions de rappel. Cette méthode d'utilisation des fonctions de rappel est la programmation asynchrone . Par exemple, le code suivant obtient le résultat via la fonction de rappel :
glob(__dirname+'/**/*', (err, res) => { résultat = res console.log('obtenir le résultat') })
Le premier paramètre de la fonction de rappel nodejs est l'erreur et les paramètres suivants sont le résultat . Pourquoi faire ça ?
essayer { entretien (fonction () { console.log('sourire') }) } attraper (erreur) { console.log('cri', euh) } entretien de fonction (rappel) { setTimeout(() => { si(Math.random() < 0,1) { rappel('succès') } autre { lancer une nouvelle erreur ('échec') } }, 500) }
Après l'exécution, il n'a pas été intercepté et l'erreur a été générée globalement, provoquant le crash de l'ensemble du programme nodejs.
Il n'est pas intercepté par try catch car setTimeout rouvre la boucle d'événements. Chaque fois qu'une boucle d'événements est ouverte, un contexte de pile d'appels est régénéré. Try catch appartient à la pile d'appels de la boucle d'événements précédente. Lorsque la fonction de rappel setTimeout est exécutée, le contexte de la pile d'appels Tout est différent. Il n'y a pas de try catch dans cette nouvelle pile d'appels, donc l'erreur est générée globalement et ne peut pas être interceptée. Pour plus de détails, veuillez vous référer à cet article Problèmes lors de l’exécution d’un try catch dans une file d’attente asynchrone.
Alors que faire ? Passez l'erreur en paramètre :
function interview(callback) { setTimeout(() => { si(Math.random() < 0,5) { rappel('succès') } autre { rappel (nouvelle erreur ('échec')) } }, 500) } entretien (fonction (res) { if (res instanceof Erreur) { console.log('cri') retour } console.log('sourire') })
Mais c'est plus gênant, et vous devez faire un jugement dans le rappel, il existe donc une règle mature. Le premier paramètre est err. S'il n'existe pas, cela signifie que l'exécution est réussie.
entretien de fonction (rappel) { setTimeout(() => { si(Math.random() < 0,5) { rappel (nul, 'succès') } autre { rappel (nouvelle erreur ('échec')) } }, 500) } entretien (fonction (res) { si (rés) { retour } console.log('sourire') })La méthode d'écriture de rappel des nodejs de
entraînera non seulement la zone de rappel, mais posera également le problème du contrôle de processus asynchrone .
Le contrôle de processus asynchrone fait principalement référence à la façon de gérer la logique de concurrence lorsque la concurrence se produit. Toujours en utilisant l'exemple ci-dessus, si votre collègue interviewe deux entreprises, il ne sera pas interviewé par la troisième entreprise tant qu'il n'aura pas interviewé avec succès deux entreprises. Alors, comment écrire cette logique ? Vous devez ajouter une variable count globalement :
var count = 0 entretien((err) => { si (erreur) { retour } compte++ si (compte >= 2) { // Logique de traitement} }) entretien((err) => { si (erreur) { retour } compte++ si (compte >= 2) { // Logique de traitement} })
Écrire comme ci-dessus est très gênant et moche. Par conséquent, les méthodes d’écriture promise et async/await sont apparues plus tard.
que la boucle d'événements actuelle ne peut pas obtenir le résultat, mais la boucle d'événements future vous donnera le résultat. C'est très similaire à ce que dirait un salaud.
La promesse n'est pas seulement un salaud, mais aussi une machine à états :
const pro = new Promise((resolve, rejet) => { setTimeout(() => { résoudre('2') }, 200) }) console.log(pro) // Print : Promise { <ending> }
L'exécution de then ou catch renverra une nouvelle promesse .L'état final de la promesse est déterminé par les résultats d'exécution des fonctions de rappel de then et catch :
fonction interview() { renvoyer une nouvelle promesse ((résoudre, rejeter) => { setTimeout(() => { si (Math.random() > 0,5) { résoudre('succès') } autre { rejeter (nouvelle erreur ('échec')) } }) }) } var promesse = interview() var promesse1 = promesse.then(() => { renvoyer une nouvelle promesse ((résoudre, rejeter) => { setTimeout(() => { résoudre('accepter') }, 400) }) })
L'état de promesse1 est déterminé par l'état de promesse en retour, c'est-à-dire l'état de promesse1 après l'exécution de la promesse en retour. Quels en sont les avantages ? Cela résout le problème de l'enfer des rappels .
var promesse = interview() .puis(() => { retour d'entretien() }) .puis(() => { retour d'entretien() }) .puis(() => { retour d'entretien() }) .catch(e => { console.log(e) })
Ensuite, si le statut de la promesse renvoyée est rejeté, alors le premier catch sera appelé et le suivant ne sera pas appelé. N'oubliez pas : les appels rejetés sont la première capture, et les appels résolus sont ensuite la première.
Si la promesse sert uniquement à résoudre les rappels infernaux, elle est trop petite pour sous-estimer la promesse. La fonction principale de la promesse est de résoudre le problème du contrôle des processus asynchrones. Si vous souhaitez interviewer deux entreprises en même temps :
function interview() { renvoyer une nouvelle promesse ((résoudre, rejeter) => { setTimeout(() => { si (Math.random() > 0,5) { résoudre('succès') } autre { rejeter (nouvelle erreur ('échec')) } }) }) } promesse .all([interview(), interview()]) .puis(() => { console.log('sourire') }) // Si une entreprise refuse, rattrapez-la .catch(() => { console.log('cri') })
Qu'est-ce que sync/await exactement :
console.log(async function() { retour 4 }) console.log(fonction() { renvoyer une nouvelle promesse ((résoudre, rejeter) => { résoudre (4) }) })
Le résultat imprimé est le même, c'est-à-dire que async/await n'est qu'un sucre syntaxique pour la promesse.
Nous savons que try catch capture les erreurs en fonction de la pile d'appels et ne peut capturer que les erreurs situées au-dessus de la pile d'appels. Mais si vous utilisez wait, vous pouvez détecter des erreurs dans toutes les fonctions de la pile d'appels. Même si l'erreur est générée dans la pile d'appels d'une autre boucle d'événements, telle que setTimeout.
Après avoir transformé le code de l'entretien, vous pouvez voir que le code a été considérablement rationalisé.
essayer { attendre un entretien(1) attendre un entretien (2) attendre un entretien (2) } capture(e => { console.log(e) })
Et s'il s'agit d'une tâche parallèle ?
wait Promise.all([interview(1), interview(2)])
En raison des E/S non bloquantes de nodejs, il est nécessaire d'utiliser des méthodes basées sur les événements pour obtenir des résultats d'E/S Pour réaliser l'événement. Pour obtenir des résultats, vous devez utiliser une programmation asynchrone, telle que des fonctions de rappel. Alors comment exécuter ces fonctions de rappel afin d’obtenir les résultats ? Ensuite, vous devez utiliser une boucle d'événements.
La boucle d'événements est la base clé pour réaliser la fonction d'E/S non bloquante de nodejs. Les E/S non bloquantes et la boucle d'événements sont des fonctionnalités fournies par la bibliothèque C++ libuv
.
Démonstration de code :
const eventloop = { file d'attente: [], boucle() { while(this.queue.length) { const rappel = this.queue.shift() rappel() } setTimeout(this.loop.bind(this), 50) }, ajouter (rappel) { this.queue.push (rappel) } } eventloop.loop() setTimeout(() => { eventloop.add(() => { console.log('1') }) }, 500) setTimeout(() => { eventloop.add(() => { console.log('2') }) }, 800)
setTimeout(this.loop.bind(this), 50)
garantit qu'après 50 ms, il vérifiera s'il y a un rappel dans la file d'attente et, si c'est le cas, l'exécutera. Cela forme une boucle d'événements.
Bien sûr, les événements réels sont beaucoup plus compliqués et il existe plus d'une file d'attente. Par exemple, il existe une file d'attente pour les opérations sur les fichiers et une file d'attente temporelle.
const boucle d'événement = { file d'attente: [], fsQueue : [], timerQueue : [], boucle() { while(this.queue.length) { const rappel = this.queue.shift() rappel() } this.fsQueue.forEach(callback => { si (fait) { rappel() } }) setTimeout(this.loop.bind(this), 50) }, ajouter (rappel) { this.queue.push (rappel) } }
Tout d'abord, nous comprenons ce qu'est une E/S non bloquante, c'est-à-dire que nous ignorons immédiatement l'exécution des tâches suivantes lorsque nous rencontrons des E/S et n'attendrons pas le résultat des E/S. Lorsque les E/S sont traitées, la fonction de traitement d'événements que nous avons enregistrée sera appelée, qui est appelée pilotée par les événements. La programmation asynchrone est nécessaire pour implémenter le lecteur d'événements. La programmation asynchrone est le lien le plus important dans nodejs. Elle va de la fonction de rappel à la promesse et enfin à l'asynchrone/attendre (en utilisant la méthode synchrone pour écrire une logique asynchrone).