Mit der Entwicklung des Front-Ends wird das Wort „asynchron“ immer häufiger verwendet. Angenommen, wir haben jetzt eine solche asynchrone Aufgabe:
Initiieren Sie mehrere Anfragen an den Server und die Ergebnisse jeder Anfrage werden als Parameter für die nächste Anfrage verwendet.
Werfen wir einen Blick darauf, was wir tun müssen:
Rückrufe
Das erste, was mir in den Sinn kommt und am häufigsten verwendet wird, ist die Rückruffunktion: Machen wir eine einfache Kapselung:
let makeAjaxCall = (url, cb) => { // do some ajax // callback with result } makeAjaxCall('http://url1', (result) => { result = JSON.parse(result) })
Hmm, sieht ziemlich gut aus! Aber wenn wir versuchen, mehrere Aufgaben zu verschachteln, sieht der Code so aus:
makeAjaxCall('http://url1', (result) => { result = JSON.parse(result) makeAjaxCall(`http://url2?q=${result.query}`, (result) => { result = JSON.parse(result) makeAjaxCall(`http://url3?q=${result.query}`, (result) => { // ... }) }) })
Oh mein Gott! Lass diesen Haufen }) zur Hölle fahren!
Wir möchten also versuchen, das JavaScript-Ereignismodell zu verwenden:
1. Pub/Sub
Bei der Verarbeitung von DOM-Ereignissen ist Pub/Sub ein sehr verbreiteter Mechanismus. Beispielsweise müssen wir Elementen eine Ereignisüberwachung hinzufügen:
elem.addEventListener(type, (evt) => { // handler })
Können wir also ein ähnliches Modell erstellen, um asynchrone Aufgaben zu bewältigen?
Der erste Schritt besteht darin, ein Vertriebszentrum aufzubauen und die On/Emit-Methode hinzuzufügen:
let PubSub = { events: {}, on(type, handler) { let events = this.events events[type] = events[type] || [] events[type].push(handler) }, emit(type, ...datas) { let events = this.events if (!events[type]) { return } events[type].forEach((handler) => handler(...datas)) } }
Dann können wir es so verwenden:
const urls = [ 'http://url1', 'http://url2', 'http://url3' ] let makeAjaxCall = (url) => { // do some ajax PubSub.emit('ajaxEnd', result) } let subscribe = (urls) => { let index = 0 PubSub.on('ajaxEnd', (result) => { result = JSON.parse(result) if (urls[++index]) { makeAjaxCall(`${urls[index]}?q=${result.query}`) } }) makeAjaxCall(urls[0]) }
Im Vergleich zur Callback-Funktion scheint es keine revolutionäre Änderung zu geben, aber der Vorteil besteht darin, dass wir die Anforderungs- und Verarbeitungsfunktionen in verschiedene Module unterbringen können, um die Kopplung zu reduzieren.
2. Versprechen
Die wirklich revolutionäre Änderung ist die Promise-Spezifikation. Mit Promise können wir asynchrone Aufgaben wie diese erledigen:
let makeAjaxCall = (url) => { return new Promise((resolve, reject) => { // do some ajax resolve(result) }) } makeAjaxCall('http://url1') .then(JSON.parse) .then((result) => makeAjaxCall(`http://url2?q=${result.query}`)) .then(JSON.parse) .then((result) => makeAjaxCall(`http://url3?q=${result.query}`))
Großartig! Es ist wie eine synchrone Funktion geschrieben!
Mach dir keine Sorgen, junger Mann. Wir haben noch besseres:
3. Generatoren
Ein weiterer großer Killer von ES6 sind Generatoren[2]. In einer Generatorfunktion können wir die Ausführung der Funktion über die yield-Anweisung unterbrechen und Anweisungen über die nächste Methode außerhalb der Funktion wiederholen. Noch wichtiger ist, dass wir über die nächste Methode Daten in die Funktion einfügen können, um das Verhalten der Funktion dynamisch zu ändern Funktion. Zum Beispiel:
function* gen() { let a = yield 1 let b = yield a * 2 return b } let it = gen() it.next() // output: {value: 1, done: false} it.next(10) // a = 10, output: {value: 20, done: false} it.next(100) // b = 100, output: {value: 100, done: true}
Kapseln Sie unsere vorherige makeAjaxCall-Funktion durch einen Generator:
let makeAjaxCall = (url) => { // do some ajax iterator.next(result) } function* requests() { let result = yield makeAjaxCall('http://url1') result = JSON.parse(result) result = yield makeAjaxCall(`http://url2?q=${result.query}`) result = JSON.parse(result) result = yield makeAjaxCall(`http://url3?q=${result.query}`) } let iterator = requests() iterator.next() // get everything start
Oh! Die Logik scheint sehr klar zu sein, aber es fühlt sich so unangenehm an, den Iterator jedes Mal von außen einfügen zu müssen ...
Keine Sorge, lass uns Promise und Generator mischen und sehen, welche schwarze Magie dabei entsteht:
let makeAjaxCall = (url) => { return new Promise((resolve, reject) => { // do some ajax resolve(result) }) } let runGen = (gen) => { let it = gen() let continuer = (value, err) => { let ret try { ret = err ? it.throw(err) : it.next(value) } catch (e) { return Promise.reject(e) } if (ret.done) { return ret.value } return Promise .resolve(ret.value) .then(continuer) .catch((e) => continuer(null, e)) } return continuer() } function* requests() { let result = yield makeAjaxCall('http://url1') result = JSON.parse(result) result = yield makeAjaxCall(`http://url2?q=${result.query}`) result = JSON.parse(result) result = yield makeAjaxCall(`http://url3?q=${result.query}`) } runGen(requests)
Die runGen-Funktion sieht aus wie ein Automat, so großartig!
Eigentlich ist diese runGen-Methode eine Implementierung der asynchronen ECMAScript 7-Funktion:
4. Asynchrone Funktion
In ES7 wird eine natürlichere asynchrone Funktion[3] eingeführt. Mit der asynchronen Funktion können wir die Aufgabe wie folgt erledigen:
let makeAjaxCall = (url) => { return new Promise((resolve, reject) => { // do some ajax resolve(result) }) } ;(async () => { let result = await makeAjaxCall('http://url1') result = JSON.parse(result) result = await makeAjaxCall(`http://url2?q=${result.query}`) result = JSON.parse(result) result = await makeAjaxCall(`http://url3?q=${result.query}`) })()
Genau wie bei der Kombination von Promise und Generator oben akzeptiert das Schlüsselwort „await“ auch ein Promise. In der asynchronen Funktion werden die verbleibenden Anweisungen erst ausgeführt, nachdem die Anweisung nach dem Warten abgeschlossen ist. Der gesamte Prozess ist genau so, als würden wir die Funktion runGen verwenden, um den Generator zu kapseln.
Die oben genannten sind mehrere asynchrone JavaScript-Programmiermodi, die in diesem Artikel zusammengefasst sind. Ich hoffe, dass sie für das Lernen aller hilfreich sein werden.