In diesem Artikel werden wir das Prinzip des synchronen Schreibens asynchroner Rückrufe in Koa untersuchen. Ebenso werden wir eine Verwaltungsfunktion implementieren. Ja, wir können asynchrone Rückruffunktionen durch synchrones Schreiben schreiben.
1. Rückrufpyramide und ideale Lösung
Wir alle wissen, dass JavaScript eine asynchrone, nicht blockierende Single-Thread-Sprache ist. Das asynchrone Nichtblockieren ist sicherlich einer seiner Vorteile, aber eine große Anzahl asynchroner Vorgänge erfordert zwangsläufig eine große Anzahl von Rückruffunktionen. Insbesondere wenn asynchrone Vorgänge verschachtelt sind, tritt ein Rückrufpyramidenproblem auf, wodurch der Code sehr schlecht lesbar ist. Zum Beispiel das folgende Beispiel:
var fs = require('fs'); fs.readFile('./file1', function(err, data) { console.log(data.toString()); fs.readFile('./file2', function(err, data) { console.log(data.toString()); }) })
Dieses Beispiel liest den Inhalt von zwei Dateien nacheinander und druckt sie aus. Das Lesen von Datei2 muss durchgeführt werden, nachdem das Lesen von Datei1 abgeschlossen ist, daher muss der Vorgang ausgeführt werden nach dem Lesen von Datei1. Wird in der abgerufenen Callback-Funktion ausgeführt. Dies ist eine typische Callback-Verschachtelung, und in der tatsächlichen Programmierung stoßen wir möglicherweise auf mehr Verschachtelungsebenen. Ein solches Code-Schreiben ist zweifellos nicht elegant genug.
In unserer Vorstellung sollte eine elegantere Schreibweise eine Schreibweise sein, die synchron aussieht, aber tatsächlich asynchron ist, ähnlich der folgenden:
var data; data = readFile('./file1'); //下面的代码是第一个readFile执行完毕之后的回调部分 console.log(data.toString()); //下面的代码是第二个readFile的回调 data = readFile('./file2'); console.log(data.toString());
Wenn Sie so schreiben, wird ein Rückruf vollständig vermieden Hölle. Tatsächlich ermöglicht uns Koa, asynchrone Rückruffunktionen auf diese Weise zu schreiben:
var koa = require('koa'); var app = koa(); var request=require('some module'); app.use(function*() { var data = yield request('http://www.baidu.com'); //以下是异步回调部分 this.body = data.toString(); }) app.listen(3000);
Was genau macht Koa so magisch?
2. Generator arbeitet mit Versprechen zusammen, um asynchrones Callback-Synchronschreiben zu implementieren
Der entscheidende Punkt ist, wie im vorherigen Artikel erwähnt, dass der Generator einen ähnlichen Effekt wie „Breakpoint“ hat. Wenn es auf Yield trifft, hält es an, übergibt nach Yield die Kontrolle an die Funktion und setzt die Ausführung fort, wenn es das nächste Mal zurückkehrt.
Im obigen Koa-Beispiel kann nach der Ernte nicht irgendein Gegenstand verwendet werden! Muss von einem bestimmten Typ sein. In der Co-Funktion können Promise-, Thunk-Funktionen usw. unterstützt werden.
Im heutigen Artikel verwenden wir Promise als Beispiel, um zu analysieren und zu sehen, wie Generator und Promise verwendet werden, um eine asynchrone Synchronisation zu erreichen.
Ich verwende immer noch das erste Beispiel für das Lesen einer Datei zur Analyse. Zuerst müssen wir die Dateilesefunktion umwandeln und in ein Versprechensobjekt kapseln:
var fs = require('fs'); var readFile = function(fileName) { return new Promise(function(resolve, reject) { fs.readFile(fileName, function(err, data) { if (err) { reject(err); } else { resolve(data); } }) }) } //下面是readFile使用的示例 var tmp = readFile('./file1'); tmp.then(function(data) { console.log(data.toString()); })
Was die Verwendung von Versprechen betrifft: Wenn Sie damit nicht vertraut sind, können Sie sich die Syntax in ansehen es6. (Ich werde in naher Zukunft auch einen Artikel schreiben, um Ihnen beizubringen, wie Sie die ES5-Syntax verwenden, um ein Versprechenobjekt mit Grundfunktionen zu implementieren. Bleiben Sie also auf dem Laufenden^_^)
Einfach ausgedrückt kann Versprechen die Rückruffunktion implementieren wird in der Form Promise.then(Callback) geschrieben. Unser Ziel ist es jedoch, mit dem Generator zusammenzuarbeiten, um ein reibungsloses synchronisiertes Schreiben zu erreichen:
var fs = require('fs'); var readFile = function(fileName) { return new Promise(function(resolve, reject) { fs.readFile(fileName, function(err, data) { if (err) { reject(err); } else { resolve(data); } }) }) } //将读文件的过程放在generator中 var gen = function*() { var data = yield readFile('./file1'); console.log(data.toString()); data = yield readFile('./file2'); console.log(data.toString()); } //手动执行generator var g = gen(); var another = g.next(); //another.value就是返回的promise对象 another.value.then(function(data) { //再次调用g.next从断点处执行generator,并将data作为参数传回 var another2 = g.next(data); another2.value.then(function(data) { g.next(data); }) })
Im obigen Code geben wir den Rückruf an Der Anweisungscode wird nach yield in den Code geschrieben. Er ist vollständig synchron und verwirklicht die Idee am Anfang des Artikels.
Nach yield erhalten wir ein anderes.value ist ein Promise-Objekt. Wir können die then-Anweisung verwenden, um die Rückruffunktion zu definieren. Der Inhalt der Funktion besteht darin, die gelesenen Daten an den Generator zurückzugeben und fortzufahren Der Generator wird vom Haltepunkt aus ausgeführt.
Im Grunde ist dies das Kernprinzip der asynchronen Callback-Synchronisation. Wenn Sie mit Python vertraut sind, wissen Sie, dass es in Python das Konzept der „Coroutine“ gibt, das grundsätzlich mithilfe von Generatoren implementiert wird (I Denken Sie, wenn ich vermute, dass der Generator von es6 von Python ausgeliehen ist~)
Wir führen den obigen Code jedoch immer noch manuell aus. Genau wie im vorherigen Artikel müssen wir auch eine Ausführungsfunktion implementieren, um den Generatorprozess so zu verwalten, dass er automatisch ausgeführt werden kann!
3. Lassen Sie die Synchronisierungsrückruffunktion automatisch laufen: Schreiben Sie eine Ausführungsfunktion
Beobachten Sie sorgfältig den Teil des vorherigen Codes, der den Generator manuell ausführt, und Sie können auch ein Muster finden ermöglicht es uns, stattdessen direkt eine rekursive Funktion zu schreiben:
var run=function(gen){ var g; if(typeof gen.next==='function'){ g=gen; }else{ g=gen(); } function next(data){ var tmp=g.next(data); if(tmp.done){ return ; }else{ tmp.value.then(next); } } next(); }
Die Funktion erhält einen Generator und ermöglicht die automatische Ausführung der darin enthaltenen asynchronen Ausführung. Mit dieser Ausführungsfunktion lassen wir den vorherigen asynchronen Code automatisch ausführen:
var fs = require('fs'); var run = function(gen) { var g; if (typeof gen.next === 'function') { g = gen; } else { g = gen(); } function next(data) { var tmp = g.next(data); if (tmp.done) { return; } else { tmp.value.then(next); } } next(); } var readFile = function(fileName) { return new Promise(function(resolve, reject) { fs.readFile(fileName, function(err, data) { if (err) { reject(err); } else { resolve(data); } }) }) } //将读文件的过程放在generator中 var gen = function*() { var data = yield readFile('./file1'); console.log(data.toString()); data = yield readFile('./file2'); console.log(data.toString()); } //下面只需要将gen放入run当中即可自动执行 run(gen);
执行上述代码,即可看到终端依次打印出了file1和file2的内容。
需要指出的是,这里的run函数为了简单起见只支持promise,而实际的co函数还支持thunk等。
这样一来,co函数的两大功能基本就完整介绍了,一个是洋葱模型的流程控制,另一个是异步同步化代码的自动执行。在下一篇文章中,我将带大家对这两个功能进行整合,写出我们自己的一个co函数!