Heim  >  Artikel  >  Web-Frontend  >  Zusammenfassung und Weitergabe, um mehrere Schlüsselknoten von NodeJS zu verstehen

Zusammenfassung und Weitergabe, um mehrere Schlüsselknoten von NodeJS zu verstehen

青灯夜游
青灯夜游nach vorne
2022-07-06 20:36:502050Durchsuche

Zusammenfassung und Weitergabe, um mehrere Schlüsselknoten von NodeJS zu verstehen

Dieser Artikel ist ein persönliches Verständnis von nodejs in der tatsächlichen Entwicklung und beim Lernen. Er ist jetzt zum späteren Nachschlagen zusammengestellt. Es wäre mir eine Ehre, wenn er Sie inspirieren könnte.

Nicht blockierende E/A

E/A: Eingabe/Ausgabe, die Eingabe und Ausgabe eines Systems.

Ein System kann als Individuum verstanden werden, beispielsweise als eine Person. Wenn Sie sprechen, ist es der Output, und wenn Sie zuhören, ist es der Input.

Der Unterschied zwischen blockierender und nicht blockierender E/A besteht darin, ob das System im Zeitraum von der Eingabe bis zur Ausgabe andere Eingaben empfangen kann. Das Folgende sind zwei Beispiele, um zu veranschaulichen, was blockierende I/O und nicht-blockierende I/O sind:

1 Mahlzeiten zubereiten

Zusammenfassung und Weitergabe, um mehrere Schlüsselknoten von NodeJS zu verstehenZuerst müssen wir den Umfang eines Systems bestimmen. In diesem Beispiel stellt sich die Cafeteria-Tante ein System vor, bei dem der Kellner im Restaurant bestellt und das Essen serviert.

Wenn Sie dann zwischen der Bestellung und dem Servieren von Speisen die Bestellungen anderer Personen annehmen können, können Sie feststellen, ob es sich um blockierende oder nicht blockierende E/A handelt. Für die Cafeteria-Tante kann sie beim Bestellen nicht für andere Schüler bestellen. Erst nachdem der Student die Bestellung abgeschlossen und das Geschirr serviert hat, kann er die Bestellung des nächsten Schülers annehmen, sodass die Cafeteria-Tante den I/O sperrt.

Für den Restaurantkellner kann er den nächsten Gast nach der Bestellung und bevor der Gast das Gericht serviert, bedienen, sodass der Kellner über nicht blockierende E/A verfügt.

2. Hausarbeit erledigen

Zu diesem Zeitpunkt müssen Sie nicht auf die Waschmaschine warten, sondern können den Schreibtisch und die Kleidung aufräumen Zu diesem Zeitpunkt wird die Wäsche aufgehängt, dann dauert es insgesamt nur 25 Minuten.

Zusammenfassung und Weitergabe, um mehrere Schlüsselknoten von NodeJS zu verstehenWäsche ist eigentlich ein nicht blockierender I/O. Zwischen dem Einlegen der Kleidung in die Waschmaschine und dem Beenden des Waschvorgangs können Sie andere Dinge tun.

Der Grund, warum nicht blockierende E/A die Leistung verbessern kann, besteht darin, dass unnötiges Warten eingespart werden kann.

Der Schlüssel zum Verständnis nicht blockierender E/A ist :

Bestimmen Sie eine Systemgrenze

für E/A. Dies ist sehr kritisch, wenn das System wie im obigen Restaurantbeispiel erweitert wird, wenn das System auf das gesamte Restaurant ausgeweitet wird, dann wird der Koch definitiv ein blockierender I/O sein.
  • Können während des I/O-Prozesses andere I/Os durchgeführt werden?
  • NodeJS nicht blockierende E/A

Wie manifestiert sich die nicht blockierende E/A von NodeJS? Wie bereits erwähnt, besteht ein wichtiger Punkt beim Verständnis nicht blockierender E/A darin, zunächst eine Systemgrenze des Knotens zu bestimmen.

Wenn das Architekturdiagramm unten nach Thread-Wartung unterteilt ist, ist die gepunktete Linie links der NodeJS-Thread und die gepunktete Linie rechts der C++-Thread.

Jetzt muss der Nodejs-Thread die Datenbank abfragen. Dies ist ein typischer E/A-Vorgang. Er wird nicht auf die Ergebnisse des E/A-Vorgangs warten und eine große Menge an Daten verteilen Rechenleistung für andere zu berechnende C++-Threads.

Warten Sie, bis das Ergebnis ausgegeben wird, und geben Sie es an den NodeJS-Thread zurück. Bevor Sie das Ergebnis erhalten, kann der NodeJS-Thread auch andere E/A-Vorgänge ausführen, sodass er nicht blockiert.

Zusammenfassung und Weitergabe, um mehrere Schlüsselknoten von NodeJS zu verstehen

nodejs-Thread

entspricht dem linken Teil als Kellner und dem C++-Thread als Chef.

Die nicht blockierende E/A des Knotens wird also durch den Aufruf von C++-Worker-Threads abgeschlossen.

Wie benachrichtige ich den NodeJS-Thread, wenn der C++-Thread das Ergebnis erhält? Die Antwort ist

ereignisgesteuert

.

Ereignisgesteuert

Blockierung

: Der Prozess schläft während der E/A und wartet auf den Abschluss der E/A, bevor er mit dem nächsten Schritt fortfährt;

Nicht blockierend

: Die Funktion kehrt während der I/O sofort zurück /O, und der Prozess wartet nicht auf den E/A-Abschluss. Um dann das zurückgegebene Ergebnis zu erfahren, müssen Sie den

Ereignistreiber

verwenden. Das sogenannte

Ereignisgesteuerte

kann als das gleiche verstanden werden wie das Front-End-Klickereignis. Ich schreibe zuerst ein Klickereignis, weiß aber nicht, wann es ausgelöst wird. Der Hauptthread führt die ereignisgesteuerte Funktion aus. Dieser Modus ist auch ein Beobachtermodus, das heißt, ich höre mir zuerst das Ereignis an und führe es dann aus, wenn es ausgelöst wird.

Wie implementiert man also Event Drive? Die Antwort ist Asynchrone Programmierung.

Asynchrone Programmierung

Wie oben erwähnt, verfügt NodeJS über eine große Anzahl nicht blockierender E/A, daher müssen die Ergebnisse nicht blockierender E/A über Rückruffunktionen abgerufen werden.

Diese Methode verwendet Rückruffunktionen ist asynchrone Programmierung

. Der folgende Code erhält beispielsweise Ergebnisse über eine Rückruffunktion:
glob(__dirname+'/**/*', (err, res) => {
    result = res
    console.log('get result')
})

Spezifikation des Callback-Funktionsformats

Der erste Parameter der NodeJS-Callback-Funktion ist Fehler, und die nachfolgenden Parameter sind das Ergebnis. Warum das tun?

try {
  interview(function () {
       console.log('smile')
  })
} catch(err) {
    console.log('cry', err)
}

function interview(callback) {
    setTimeout(() => {
        if(Math.random() <p>Nach der Ausführung wurde es nicht abgefangen und der Fehler wurde global ausgelöst, was zum Absturz des gesamten NodeJS-Programms führte. </p><p><img src="https://img.php.cn/upload/image/244/886/980/1657110712466688.png" title="1657110712466688.png" alt="Zusammenfassung und Weitergabe, um mehrere Schlüsselknoten von NodeJS zu verstehen"></p><p> wird von try Catch nicht erfasst, da setTimeout die Ereignisschleife erneut öffnet. Jedes Mal, wenn eine Ereignisschleife geöffnet wird, wird ein Aufrufstapelkontext neu generiert, der zum Aufrufstapel der vorherigen Ereignisschleife und der Rückruffunktion gehört Wenn setTimeout ausgeführt wird, ist der Aufrufstapel anders. In diesem neuen Aufrufstapel gibt es keinen Try-Catch, daher wird der Fehler global ausgelöst und kann nicht abgefangen werden. Weitere Informationen finden Sie in diesem Artikel. Probleme bei der Verwendung asynchroner Warteschlangen für Try Catch. <a href="https://juejin.cn/post/6995749646366670855" target="_blank" title="https://juejin.cn/post/6995749646366670855"></a>Was sollen wir also tun? Verwenden Sie den Fehler als Parameter: </p><pre class="brush:php;toolbar:false">function interview(callback) {
    setTimeout(() => {
        if(Math.random() <p> Dies ist jedoch problematischer und muss im Rückruf beurteilt werden, sodass eine ausgereifte Konvention erstellt wird. Der erste Parameter ist err erfolgreich. </p><pre class="brush:php;toolbar:false">function interview(callback) {
    setTimeout(() => {
        if(Math.random() <p></p>Asynchrone Prozesssteuerung<h3 data-id="heading-5">
<strong></strong>Die Callback-Schreibmethode von NodeJS bringt nicht nur Callback-Bereiche mit sich, sondern auch Probleme mit der asynchronen Prozesssteuerung. </h3><p>Asynchrone Prozesssteuerung bezieht sich hauptsächlich auf die Handhabung der Parallelitätslogik, wenn Parallelität auftritt. Nehmen wir weiterhin das obige Beispiel: Wenn Ihr Kollege zwei Unternehmen interviewt, wird er erst dann von dem dritten Unternehmen interviewt, wenn er erfolgreich zwei Unternehmen interviewt hat. Wie schreibt man diese Logik? Es ist notwendig, jede Variable count:<strong><pre class="brush:php;toolbar:false">var count = 0
interview((err) => {
    if (err) {
        return
    }
    count++
    if (count >= 2) {
        // 处理逻辑
    }
})

interview((err) => {
    if (err) {
        return
    }
    count++
    if (count >= 2) {
        // 处理逻辑
    }
})
wie die obige Methode zu unterstützen, was sehr mühsam und hässlich ist. Daher erschienen später die Schreibmethoden Promise und Async/Await.

Versprechen

Die aktuelle Ereignisschleife kann das Ergebnis nicht abrufen, aber die zukünftige Ereignisschleife wird Ihnen das Ergebnis liefern. Es ist dem sehr ähnlich, was ein Drecksack sagen würde.

Versprechen ist nicht nur ein Drecksack, sondern auch eine Zustandsmaschine:

ausstehend

erfüllt/gelöst
  • abgelehnt
  • const pro = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('2')
        }, 200)
    })
    console.log(pro) // 打印:Promise { <pending> }</pending>
  • dann & .catch

gelöstes Zustandsversprechen ruf den ersten später an Ein Versprechen im dann

abgelehnten Zustand ruft den ersten Catch auf.
  • Jedes Versprechen im Ablehnungszustand, dem nicht .catch folgt, führt zu einem globalen Fehler in der Browser- oder Knotenumgebung. „uncaught“ stellt einen nicht erfassten Fehler dar.
Die Ausführung von then oder Catch wird

ein neues Versprechen zurückgebenZusammenfassung und Weitergabe, um mehrere Schlüsselknoten von NodeJS zu verstehen Der Endzustand des Versprechens wird durch die Ausführungsergebnisse der Callback-Funktionen von then und Catch bestimmt:

Wenn die Callback-Funktion immer throw new ist Fehler, das Versprechen ist im Status „Abgelehnt“

Wenn die Rückruffunktion immer zurückkehrt, befindet sich das Versprechen im gelösten Zustand
  • Aber wenn die Rückruffunktion immer ein Versprechen zurückgibt, stimmt das
  • Versprechen mit dem Versprechensstatus der Rückruffunktion überein
  • .
  • function interview() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (Math.random() > 0.5) {
                    resolve('success')
                } else {
                    reject(new Error('fail'))
                }
            })
        })
    }
    
    var promise = interview()
    var promise1 = promise.then(() => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('accept')
            }, 400)
        })
    })
    Der Status von Versprechen1 wird durch den Status des Gegenzugsversprechens bestimmt, d. h. durch den Status von Versprechen1, nachdem das Gegenzugsversprechen ausgeführt wurde. Was sind die Vorteile davon? Dies kann „das Problem der Rückrufhölle lösen“.
    var promise = interview()
        .then(() => {
            return interview()
        })
        .then(() => {
            return interview()
        })
        .then(() => {
            return interview()
        })
        .catch(e => {
            console.log(e)
        })
  • then Wenn der Status des zurückgegebenen Versprechens „Abgelehnt“ ist, wird der erste Catch aufgerufen und der nachfolgende Catch wird nicht aufgerufen. Denken Sie daran: Abgelehnte Anrufe sind der erste Fang, gelöste Anrufe dann der erste.

Versprechen löst asynchrone Prozesssteuerung

Wenn Versprechen nur dazu dienen, Höllenrückrufe zu lösen, ist es zu klein, um Versprechen zu unterschätzen. Die Hauptfunktion von Versprechen besteht darin, asynchrone Prozesssteuerungsprobleme zu lösen. Wenn Sie zwei Unternehmen gleichzeitig interviewen möchten:

function interview() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                resolve('success')
            } else {
                reject(new Error('fail'))
            }
        })
    })
}

promise
    .all([interview(), interview()])
    .then(() => {
        console.log('smile')
    })
    // 如果有一家公司rejected,就catch
    .catch(() => {
        console.log('cry')
    })

async/await

sync/await Was genau ist:

console.log(async function() {
    return 4
})

console.log(function() {
    return new Promise((resolve, reject) => {
        resolve(4)
    })
})

Die gedruckten Ergebnisse sind die gleichen, das heißt, async/await ist nur der syntaktische Zucker des Versprechens. Wir wissen, dass Try Catch Fehler erfasst,

vom Aufrufstapel abhängt und nur Fehler oberhalb des Aufrufstapels erfassen kann. Wenn Sie jedoch „await“ verwenden, können Sie Fehler in allen Funktionen im Aufrufstapel abfangen. Auch wenn der Fehler in der Aufrufliste einer anderen Ereignisschleife, z. B. setTimeout, ausgegeben wird.

Nach der Transformation des Interviewcodes können Sie sehen, dass der Code deutlich optimiert ist.

try {
    await interview(1)
    await interview(2)
    await interview(2)
} catch(e => {
    console.log(e)
})
Was ist, wenn es eine parallele Aufgabe ist?

await Promise.all([interview(1), interview(2)])
Ereignisschleife

Aufgrund der nicht blockierenden E/A von NodeJS müssen Sie einen ereignisgesteuerten Ansatz verwenden, um die E/A-Ergebnisse zu erhalten. Um ereignisgesteuerte Ergebnisse zu erzielen, müssen Sie asynchrone Programmierung verwenden , wie z. B. Rückruffunktionen. Wie führt man diese Rückruffunktionen aus, um die Ergebnisse zu erhalten? Dann müssen Sie eine Ereignisschleife verwenden.

Die Ereignisschleife ist die wichtigste Grundlage für die Realisierung der nicht blockierenden E/A-Funktion von nodejs. Nicht blockierende E/A und Ereignisschleife sind beides Funktionen, die von

dieser C++-Bibliothek bereitgestellt werden.

Zusammenfassung und Weitergabe, um mehrere Schlüsselknoten von NodeJS zu verstehen

代码演示:

const eventloop = {
    queue: [],
    loop() {
        while(this.queue.length) {
            const callback = this.queue.shift()
            callback()
        }
        setTimeout(this.loop.bind(this), 50)
    },
    add(callback) {
        this.queue.push(callback)
    }
}

eventloop.loop()

setTimeout(() => {
    eventloop.add(() => {
        console.log('1')
    })
}, 500)

setTimeout(() => {
	eventloop.add(() => {
		console.log('2')
	})
}, 800)

setTimeout(this.loop.bind(this), 50)保证了50ms就会去看队列中是否有回调,如果有就去执行。这样就形成了一个事件循环。

当然实际的事件要复杂的多,队列也不止一个,比如有一个文件操作对列,一个时间对列。

const eventloop = {
    queue: [],
    fsQueue: [],
    timerQueue: [],
    loop() {
        while(this.queue.length) {
            const callback = this.queue.shift()
            callback()
        }
        this.fsQueue.forEach(callback => {
            if (done) {
                callback()
            }
        })
        setTimeout(this.loop.bind(this), 50)
    },
    add(callback) {
        this.queue.push(callback)
    }
}

总结

首先我们弄清楚了什么是非阻塞I/O,即遇到I/O立刻跳过执行后面的任务,不会等待I/O的结果。当I/O处理好了之后就会调用我们注册的事件处理函数,这就叫事件驱动。实现事件驱动就必须要用异步编程,异步编程是nodejs中最重要的环节,它从回调函数到promise,最后到async/await(使用同步的方法写异步逻辑)。

更多node相关知识,请访问:nodejs 教程

Das obige ist der detaillierte Inhalt vonZusammenfassung und Weitergabe, um mehrere Schlüsselknoten von NodeJS zu verstehen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen