Heim >Web-Frontend >js-Tutorial >Eine eingehende Analyse der asynchronen Rückrufverarbeitung von koa

Eine eingehende Analyse der asynchronen Rückrufverarbeitung von koa

高洛峰
高洛峰Original
2016-11-19 15:44:441054Durchsuche

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);

Führen Sie den obigen Code aus, und Sie können sehen, dass das Terminal den Inhalt von Datei1 und Datei2 nacheinander ausgibt.

Es sollte darauf hingewiesen werden, dass die Run-Funktion hier der Einfachheit halber nur Promises unterstützt, während die eigentliche Co-Funktion auch Thunks usw. unterstützt.

Auf diese Weise werden die beiden Hauptfunktionen der Co-Funktion im Wesentlichen vollständig eingeführt. Eine davon ist die Prozesssteuerung des Zwiebelmodells und die andere die automatische Ausführung von asynchronem Synchronisationscode. Im nächsten Artikel werde ich Sie dazu bringen, diese beiden Funktionen zu integrieren und unsere eigene Co-Funktion zu schreiben!

Der Code dieses Artikels ist auch auf Github zu finden: https://github.com/mly-zju/async-js-demo, wobei Promise_generator.js der Beispielquellcode dieses Artikels ist.


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn