Entwurf asynchroner Pipelines für eine effiziente Datenverarbeitung
Notiz. Dieser Artikel setzt bereits voraus, dass Sie mit Callbacks und Guarantees vertraut sind und über ein grundlegendes Verständnis des asynchronen Paradigmas in JavaScript verfügen.
Der asynchrone Mechanismus ist eines der wichtigsten Konzepte in JavaScript und der Programmierung im Allgemeinen. Er ermöglicht es einem Programm, sekundäre Aufgaben separat im Hintergrund auszuführen, ohne den aktuellen Thread daran zu hindern, primäre Aufgaben auszuführen. Wenn eine sekundäre Aufgabe abgeschlossen ist, wird ihr Ergebnis zurückgegeben und das Programm wird regular weiter ausgeführt. In diesem Zusammenhang werden solche sekundären Aufgaben genannt asynchron.
Zu asynchronen Aufgaben gehören typischerweise Anfragen an externe Umgebungen wie Datenbanken, Net-APIs oder Betriebssysteme.Wenn das Ergebnis einer asynchronen Operation die Logik des Hauptprogramms nicht beeinflusst, ist es viel besser, diese Zeit nicht zu verschwenden und mit der Ausführung primärer Aufgaben fortzufahren, anstatt einfach zu warten, bis die Aufgabe abgeschlossen ist.
Dennoch kommt es vor, dass das Ergebnis einer asynchronen Operation sofort in den nächsten Codezeilen verwendet wird. In solchen Fällen sollten die nachfolgenden Codezeilen erst ausgeführt werden, wenn die asynchrone Operation abgeschlossen ist.
Notiz. Bevor ich zum Hauptteil dieses Artikels komme, möchte ich die Gründe dafür darlegen, warum Asynchronität als wichtiges Thema in der Datenwissenschaft angesehen wird und warum ich JavaScript anstelle von Python verwendet habe, um die
async / await
Syntax.
Datentechnik ist ein untrennbarer Teil der Datenwissenschaft, die hauptsächlich aus der Entwicklung robuster und effizienter Datenpipelines besteht. Zu den typischen Aufgaben der Datentechnik gehören regelmäßige Aufrufe von APIs, Datenbanken oder anderen Quellen, um Daten abzurufen, zu verarbeiten und irgendwo zu speichern.
Stellen Sie sich eine Datenquelle vor, die Netzwerkprobleme hat und die angeforderten Daten nicht sofort zurückgeben kann. Wenn wir die Anforderung einfach im Code an diesen Dienst stellen, müssen wir ziemlich lange warten und tun nichts. Wäre das nicht Wäre es beispielsweise besser, keine kostbare Prozessorzeit zu verschwenden und stattdessen eine andere Funktion auszuführen? Hier kommt die Macht der Asynchronität ins Spiel, die das zentrale Thema dieses Artikels sein wird!
Niemand wird die Tatsache leugnen, dass Python derzeit die beliebteste Wahl für die Erstellung von Information Science-Anwendungen ist. Dennoch ist JavaScript eine weitere Sprache mit einem riesigen Ökosystem, das verschiedenen Entwicklungszwecken dient, einschließlich der Erstellung von Webanwendungen, die von anderen Diensten abgerufene Daten verarbeiten. Wie sich herausstellt, spielt Asynchronität eine der grundlegendsten Rollen in JavaScript.
Darüber hinaus verfügt JavaScript im Vergleich zu Python über eine umfassendere integrierte Unterstützung für den Umgang mit Asynchronität und dient im Allgemeinen als besseres Beispiel, um tiefer in dieses Thema einzutauchen.
Schließlich hat Python eine ähnliche async / await
Konstruktion. Daher können die in diesem Artikel über JavaScript vorgestellten Informationen auch auf Python übertragen werden, um effiziente Datenpipelines zu entwerfen.
In den ersten Versionen von JavaScript wurde asynchroner Code hauptsächlich mit Callbacks geschrieben. Leider führte dies bei Entwicklern zu einem bekannten Drawback namens „Rückruf Hölle”. Asynchroner Code, der mit einfachen Callbacks geschrieben wurde, führte häufig zu mehreren verschachtelten Codebereichen, die extrem schwer zu lesen waren. Aus diesem Grund führten die JavaScript-Entwickler 2012 Folgendes ein: Versprechen.
// Instance of the "callback hell" downsidefunctionOne(perform () {
functionTwo(perform () {
functionThree(perform () {
functionFour(perform () {
...
});
});
});
});
Guarantees bieten eine praktische Schnittstelle für die asynchrone Code-Entwicklung. Ein Promise nimmt in einen Konstruktor eine asynchrone Funktion auf, die zu einem bestimmten Zeitpunkt in der Zukunft ausgeführt wird. Bevor die Funktion ausgeführt wird, befindet sich das Promise in einem ausstehend Zustand. Abhängig davon, ob die asynchrone Funktion erfolgreich abgeschlossen wurde oder nicht, ändert das Promise seinen Zustand entweder in erfüllt oder abgelehnt Für die letzten beiden Zustände können Programmierer .then()
Und .catch()
Methoden mit dem Versprechen, die Logik zu deklarieren, wie das Ergebnis der asynchronen Funktion in verschiedenen Szenarien behandelt werden soll.
Außerdem kann eine Gruppe von Guarantees durch Kombinationsmethoden wie any()
, all()
, race()
usw.
Obwohl Guarantees eine deutliche Verbesserung gegenüber Callbacks darstellen, sind sie aus mehreren Gründen immer noch nicht ultimate:
- Ausführlichkeit. Guarantees erfordern normalerweise das Schreiben einer Menge Boilerplate-Code. In manchen Fällen erfordert das Erstellen eines Guarantees mit einer einfachen Funktionalität aufgrund der ausführlichen Syntax ein paar zusätzliche Codezeilen.
- Lesbarkeit. Wenn mehrere Aufgaben voneinander abhängig sind, führt dies dazu, dass Guarantees ineinander verschachtelt werden. Dieses berüchtigte Drawback ist sehr ähnlich dem „Rückrufhölle“ Dies erschwert das Lesen und Warten des Codes. Darüber hinaus ist es bei der Fehlerbehandlung normalerweise schwierig, der Codelogik zu folgen, wenn ein Fehler über mehrere Promise-Ketten propagiert wird.
- Debuggen. Durch Überprüfen der Stacktrace-Ausgabe kann es schwierig sein, die Fehlerquelle in Guarantees zu identifizieren, da diese normalerweise keine eindeutigen Fehlerbeschreibungen liefern.
- Integration mit älteren Bibliotheken. Viele ältere Bibliotheken in JavaScript wurden in der Vergangenheit für die Arbeit mit einfachen Callbacks entwickelt, wodurch sie nicht ohne weiteres mit Guarantees kompatibel sind. Wenn Code mithilfe von Guarantees geschrieben wird, sollten zusätzliche Codekomponenten erstellt werden, um die Kompatibilität mit alten Bibliotheken zu gewährleisten.
Zum größten Teil async / await
development wurde in JavaScript als synthetischer Zucker anstelle von Guarantees hinzugefügt. Wie der Identify schon sagt, führt es zwei neue Code-Schlüsselwörter ein:
async
wird vor der Funktionssignatur verwendet und markiert die Funktion als asynchron, was immer ein Versprechen zurückgibt (auch wenn ein Versprechen nicht explizit zurückgegeben wird, da es implizit verpackt wird).await
wird innerhalb von Funktionen verwendet, die als gekennzeichnet sind asynchron und wird im Code vor asynchronen Operationen deklariert, die ein Versprechen zurückgeben. Enthält eine Codezeile denawait
Schlüsselwort, dann werden die folgenden Codezeilen innerhalb der asynchronen Funktion nicht ausgeführt, bis das zurückgegebene Versprechen erfüllt ist (entweder in der erfüllt oder abgelehnt Zustand)Dadurch wird sichergestellt, dass die folgenden Zeilen nicht ausgeführt werden, wenn ihre Ausführungslogik vom Ergebnis der asynchronen Operation abhängt.
– Der
await
Schlüsselwort kann innerhalb einer asynchronen Funktion mehrmals verwendet werden.– Wenn
await
innerhalb einer Funktion verwendet wird, die nicht als asynchron markiert ist,SyntaxError
wird geworfen.– Das zurückgegebene Ergebnis einer Funktion, die mit
await
es die aufgelöster Wert eines Versprechens.
Der async / await
Ein Anwendungsbeispiel wird im folgenden Snippet gezeigt.
// Async / await instance.
// The code snippet prints begin and finish phrases to the console.perform getPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('finish');
},
1000);
});
}
// since this perform is marked as async, it should return a promise
async perform printInformation() {
console.log('begin');
const end result = await getPromise();
console.log(end result) // this line is not going to be executed till the promise is resolved
}
Es ist wichtig zu verstehen, dass await die Ausführung des Haupt-JavaScript-Threads nicht blockiert. Stattdessen unterbricht es nur die einschließende asynchrone Funktion (während anderer Programmcode außerhalb der asynchronen Funktion ausgeführt werden kann).
Fehlerbehandlung
Der async / await
Konstruktion bietet eine Standardmethode zur Fehlerbehandlung mit strive / catch
Schlüsselwörter. Um Fehler zu behandeln, ist es notwendig, den gesamten Code, der möglicherweise einen Fehler verursachen kann, zu umschließen (einschließlich await
Erklärungen) in der strive
Block und schreiben Sie entsprechende Deal with-Mechanismen in die catch
Block.
In der Praxis ist die Fehlerbehandlung mit
strive / catch
Blöcke ist einfacher und lesbarer als das gleiche in Versprechen mit zu erreichen.catch()
Ablehnungsverkettung.
// Error dealing with template inside an async performasync perform functionOne() {
strive {
...
const end result = await functionTwo()
} catch (error) {
...
}
}
async / await
ist eine großartige Different zu Guarantees. Sie beseitigen die oben genannten Nachteile von Guarantees: Der mit async / await
ist normalerweise besser lesbar und wartungsfreundlicher und wird von den meisten Softwareentwicklern bevorzugt.
Es wäre jedoch falsch, die Bedeutung von Guarantees in JavaScript zu leugnen: In manchen Situationen sind sie die bessere Possibility, insbesondere beim Arbeiten mit Funktionen, die standardmäßig ein Promise zurückgeben.
Code-Austauschbarkeit
Betrachten wir den gleichen Code, geschrieben mit async / await
und Versprechen. Wir gehen davon aus, dass unser Programm eine Verbindung zu einer Datenbank herstellt und im Falle einer hergestellten Verbindung Daten über Benutzer anfordert, um diese anschließend in der Benutzeroberfläche anzuzeigen.
// Instance of asynchronous requests dealt with by async / awaitasync perform functionOne() {
strive {
...
const end result = await functionTwo()
} catch (error) {
...
}
}
Beide asynchronen Anfragen können einfach verpackt werden, indem man await
Syntax. Bei jedem dieser beiden Schritte stoppt das Programm die Codeausführung, bis die Antwort abgerufen wird.
Da bei asynchronen Anfragen immer etwas schief gehen kann (Verbindungsabbruch, Dateninkonsistenz, and many others.), sollten wir das ganze Codefragment in ein strive / catch
Block. Wenn ein Fehler auftritt, zeigen wir ihn auf der Konsole an.
Schreiben wir nun dasselbe Codefragment mit Guarantees:
// Instance of asynchronous requests dealt with by guaranteesperform displayUsers() {
...
connectToDatabase()
.then((response) => {
...
return getData(information);
})
.then((customers) => {
showUsers(customers);
...
})
.catch((error) => {
console.log(`An error occurred: ${error.message}`);
...
});
}
Dieser verschachtelte Code sieht ausführlicher und schwerer lesbar aus. Außerdem können wir feststellen, dass jede await-Anweisung in eine entsprechende then()
-Methode und dass sich der Catch-Block nun innerhalb der .catch()
Methode eines Versprechens.
Der gleichen Logik folgend, jeder
async / await
Code kann mit Guarantees umgeschrieben werdenDiese Aussage belegt die Tatsache, dassasync / await
ist bloß synthetischer Zucker statt Versprechungen.
Mit async / await geschriebener Code kann in die Promise-Syntax umgewandelt werden, wobei jede Await-Deklaration einer separaten .then()-Methode entspricht und die Ausnahmebehandlung in der .catch()-Methode durchgeführt wird.
In diesem Abschnitt werden wir uns ein reales Beispiel ansehen, wie async / await
funktioniert.
Wir verwenden die REST-Länder-API das demografische Informationen für ein angefordertes Land im JSON-Format unter der folgenden URL-Adresse bereitstellt: https://restcountries.com/v3.1/identify/$nation
.
Lassen Sie uns zunächst eine Funktion deklarieren, die die wichtigsten Informationen aus dem JSON abruft. Wir möchten Informationen zum Namen des Landes, seiner Hauptstadt, seiner Fläche und seiner Bevölkerung abrufen. Das JSON wird in Type eines Arrays zurückgegeben, wobei das erste Objekt alle erforderlichen Informationen enthält. Wir können auf die oben genannten Eigenschaften zugreifen, indem wir auf die Schlüssel des Objekts mit den entsprechenden Namen zugreifen.
const retrieveInformation = perform (information) {
information = information(0)
return {
nation: information("identify")("widespread"),
capital: information("capital")(0),
space: `${information("space")} km`,
inhabitants: `{$information("inhabitants")} individuals`
};
};
Dann verwenden wir die API abrufen um HTTP-Anfragen auszuführen. Fetch ist eine asynchrone Funktion, die gibt ein Versprechen zurück. Da wir die von fetch zurückgegebenen Daten sofort benötigen, müssen wir warten, bis fetch seinen Job beendet hat, bevor wir die folgenden Codezeilen ausführen. Dazu verwenden wir den await
Schlüsselwort vor dem Abrufen.
// Fetch instance with async / awaitconst getCountryDescription = async perform (nation) {
strive {
const response = await fetch(
`https://restcountries.com/v3.1/identify/${nation}`
);
if (!response.okay) {
throw new Error(`Unhealthy HTTP standing of the request (${response.standing}).`);
}
const information = await response.json();
console.log(retrieveInformation(information));
} catch (error) {
console.log(
`An error occurred whereas processing the request.nError message: ${error.message}`
);
}
};
Ebenso platzieren wir ein weiteres await
vor dem .json()
Methode zum Parsen der Daten, die unmittelbar danach im Code verwendet wird. Im Falle eines fehlerhaften Antwortstatus oder einer Unfähigkeit, die Daten zu parsen, wird ein Fehler ausgegeben, der dann im Catch-Block verarbeitet wird.
Zu Demonstrationszwecken schreiben wir den Codeausschnitt auch mithilfe von Guarantees neu:
// Fetch instance with guaranteesconst getCountryDescription = perform (nation) {
fetch(`https://restcountries.com/v3.1/identify/${nation}`)
.then((response) => {
if (!response.okay) {
throw new Error(`Unhealthy HTTP standing of the request (${response.standing}).`);
}
return response.json();
})
.then((information) => {
console.log(retrieveInformation(information));
})
.catch((error) => {
console.log(
`An error occurred whereas processing the request. Error message: ${error.message}`
);
});
};
Wenn Sie eine Both-Funktion mit einem angegebenen Ländernamen aufrufen, werden dessen Hauptinformationen gedruckt:
// The results of calling getCountryDescription("Argentina"){
nation: 'Argentina',
capital: 'Buenos Aires',
space: '27804000 km',
inhabitants: '45376763 individuals'
}
In diesem Artikel haben wir die async / await
Konstruktion in JavaScript, die 2017 in der Sprache erschien. Als Verbesserung gegenüber Guarantees ermöglicht es das synchrone Schreiben von asynchronem Code, wodurch verschachtelte Codefragmente eliminiert werden. Die korrekte Verwendung in Kombination mit Guarantees ergibt eine leistungsstarke Mischung, die den Code so sauber wie möglich macht.
Schließlich sind die in diesem Artikel über JavaScript präsentierten Informationen auch für Python wertvoll, das hat das gleiche async / await
Konstruktion. Wenn jemand tiefer in die Asynchronität eintauchen möchte, würde ich persönlich empfehlen, sich mehr auf JavaScript als auf Python zu konzentrieren. Wenn man sich der zahlreichen Instruments bewusst ist, die in JavaScript für die Entwicklung asynchroner Anwendungen zur Verfügung stehen, kann man die gleichen Konzepte auch in anderen Programmiersprachen leichter verstehen.
Alle Bilder stammen, sofern nicht anders angegeben, vom Autor.