NEWS
Kann mir jemand dieses JavaScript-Konzept erklären?
-
Hallo zusammen,
ich versuche gerade, zu verstehen, wie man in JavaScript mit Funktionen arbeitet, die wiederum Funktionen als Parameter übergeben bekommen.
Konkret geht es mir um die Funktion getHistory: https://github.com/ioBroker/ioBroker.ja … gethistory
getHistory (instance, options, function (error, result, options, instance) {});
Was ich schon meine verstanden zu haben:
Ich kann diese Funktion aufrufen und an der Stelle, wo function(…) steht, meine eigene Funktion direkt in den Aufruf schreiben - ein recht gewöhnungsbedürftiges Konzept, aber ok.
Innerhalb meiner Funktion kann ich dann auch auf alle Inhalte des Objekts result zugreifen, z.B. die Summe aller Werte berechnen und diese mittels console.log() ausgeben.
Aber wie kann ich von dort aus wieder Werte an den Aufrufer von getHistory zurückgeben?
Was ich gerne machen würde, hätte in den mir bisher geläufigen Sprachen etwa folgende Struktur:
function DatenAusHistoryHolen(...) { result = getHistory(...); ... weiterer Code, der aus result mein eigentliches Ergebnis berechnet ... return MeinBerechnetesErgebnis; }
Die derart definierte Funktion DatenAusHistoryHolen() würde ich dann gerne mehrfach aufrufen. Derzeit scheitere ich aber daran, dass ich die Ergebnisse von getHistory() zwar innerhalb der "Parameter-Funktion" nutzen kann, aber keine Syntax gefunden habe, mit der ich sie von dort aus an den Aufrufer von getHistory() zurückgeben kann.
Entweder kann mir jetzt jemand einfach nur die richtige Syntax zeigen, mit der ich dieses Ziel erreiche, oder ich habe grundlegende Konzepte von JS noch überhaupt nicht verstanden…
-
Hallo,
Dein Problem ist vermutlich nicht Javascript, sondern das Konzept der asynchronous callback.
Ein klassisches Programm könnte so aussehen:
var s=warteAufEingabe(); console.log(s); ... tue sonst etwas
Hinweis: dieses Programm ist ein Beispiel und fkt. so nicht!
Das Programm wird sequentiell abgearbeitet. Zunächst wird auf eine Eingabe gewartet. Solange diese nicht erfolgt, ist das Programm angehalten. Erfolgt die Eingabe, wird sie ausgegeben. Alles hört sich vernünftig an. Aber, was machst Du, wenn z.B. einer den Lichtschalter drückt. Dein weiteres Programm kann nicht reagieren und hängt in der Warteschleife und wartet auf die Eingabe.
Wie kann man das Dilemma beheben? Grundsätzlich könnte man für jedes Progrämmchen eine eigene Instanz von Javascript starten, damit diese parallel laufen. Da nodejs (das Grundgerüst von ioBroker) auch auf schwachen Rechnern laufen soll, kommt dieses Verfahren nicht in Betracht.
Vielmehr soll das Programm nicht in einer Schleife "hängen", sondern weiter ausgeführt. Um auf das Ereignis der Eingabe reagieren zu können übergibt man eine Callback-Funktion:
warteAufEingabe(function (s) {console.log(s);}); ... tue sonst etwas
Nach dem Aufruf der Funktion warteAufEingabe() gibt diese sofort die Kontrolle an das aufrufende Programm zurück. Es wird also anders als im ersten Programm sofort "… tue sonst etwas" ausgeführt
Erfolgt eine Eingabe wird die Callback-Funktion aufgerufen. In dieser Funktion verarbeitest Du dann das Ergebnis der Funktion warteAufEingabe(). Hier als Beispiel eine Ausgabe auf der Konsole. Die ursprüngliche Funktion bestimmt, welche Rückgabewerte sie zur Verfügung stellt. In meinem Beispiel das Ergebnis der Eingabe. Daher übergibt warteAufEingabe() der Callback-Funktion hier genau einen Parameter. In de Deklaration der Callback-Funktion ist daher genau eine Variable angegeben (hier s). In der Funktion kannst Du s dann beliebig verarbeiten.
Jetzt stellst Du Dir sicherlich die Frage, warum Du das nicht so formulieren kannst:
var eingabe="leer"; warteAufEingabe(function (s) {eingabe=s;} console.log(eingabe); ... tue sonst etwas
Das Programm würde dann so auch tatsächlich ausgeführt. Nur das Ergebnis würde Dir nicht gefallen. Es wird als Ergebnis Dir nicht Deine Eingabe, sondern immer nur "leer" ausgeben. Aber warum? "eingabe" ist eine Globale Variable. DU setzt den Wert auch richtig in der Callback-Funktion. Jetzt kommt aber das ABER: Die Zeile "console.log(eingabe);" wird direkt nach dem Funktionsaufruf warteAufEingabe() ausgeführt. Zu diesen Zeitpunkt hast Du noch keine Eingabe gemacht. Daher hat die Variable "eingabe" immer die Wert "leer". Daher ist es wichtig die Verarbeitung in der Callback-Funktion zu machen. Falls es mal sinnvoll (oder notwendig) ist kann man das Ergebnis in einem State (mit setState nachdem man den State angelegt hat) speichern. Man kann dann mittels Ereignis per on() darauf reagieren. Wie Du siehst ist das mal wieder eine Callback-Funktion.
Ich hoffe ich konnte Dir das Thema etwa näher bringen.
-
Mein Vorposter hats schon sehr gut erklärt, warum die Dinge so sind wie sie sind. Im Prinzip geht es darum, anderen Programmteilen Zeit zu geben, abzulaufen, während auf länger dauernde Prozesse gewartet wird.
Es gibt Bestreben in JavaScript, mit dem Konstrukt async/await die äußere Form eines klassischen, synchronen Programms wiederherzustellen, während unter der Haube Callbacks hin und her ausgeführt werden. Etwa so wie du es vorhast:
async function doSomething() { var data = await getHistoryAsync(...); // etwas mit data tun. }
Das ist intern allerdings etwas komplizierter. Dazu müsste eine Funktion getHistoryAsync existieren, die einen Promise (Wert, der erst zu einem späteren Zeitpunkt befüllt wird) zurückgibt. Die asynchrone Funktion doSomething wird dann an dieser Stelle pausiert, anderer Code an anderer Stelle läuft ab, und wenn getHistoryAsync irgendwann mal einen Wert zurückgibt, wird doSomething dort fortgesetzt, wo sie zuvor pausiert wurde.
Die Spezifikation hierfür ist allerdings noch nicht mal fertig und die Unterstützung nicht allzuweit fortgeschritten (z.B. erst ab NodeJS 7.10). Aktuell muss z.B. auf TypeScript oder Transpiler wie Babel zurückgegriffen werden, wenn man das verwenden will.
-
Vielen Dank für diese ausführlichen Antworten! Das wird hier langsam zu einem ausgewachsenen FAQ-Artikel
Dass Node.js offenbar so grundlegend alles über Callbacks löst, hatte ich tatsächlich nicht erwartet. Da muss ich meine Programmiergewohnheiten doch gewaltig umstellen
Mir ist noch etwas unklar, wie man das Konstrukt, was ich gerade benötige in dieser Welt am besten umsetzt. Gibt's da vielleicht ein passendes Design-Pattern?
Ich möchte eine Funktion haben, die mittels getHistory ein Zeitintervall eines Objektes holt und daraus Zwischenergebnisse berechnet.
Diese Funktion soll im Haupt-Script (bzw. in einer anderen Funktion) mehrfach mit verschiedenen Objekt-IDs aufgerufen werden und dann aus den verschiedenen Zwischenergebnissen ein Endergebnis berechnen.
Wie finden die Zwischenergebnisse nun am sinnvollsten ihren Weg zu dem Programmteil, der mit ihnen weiterrechnet?
Muss ich für jedes Zwischenergebnis einen State anlegen?
Kann der Datentyp eines States auch ein Objekt sein?
Kann die weiterführende Funktion auch auf die Aktualisierung mehrerer States warten, bevor sie ausgelöst wird?
Oder könnte ich auch für jedes Zwischenergebnis eine globale Variable in meinem Script anlegen und diese by-Reference übergeben?
Gibt es sowas in JS überhaupt? (In C wäre das einfach ein Pointer auf die Variable.)
-
Ich möchte eine Funktion haben, die mittels getHistory ein Zeitintervall eines Objektes holt und daraus Zwischenergebnisse berechnet.
Diese Funktion soll im Haupt-Script (bzw. in einer anderen Funktion) mehrfach mit verschiedenen Objekt-IDs aufgerufen werden und dann aus den verschiedenen Zwischenergebnissen ein Endergebnis berechnen.
Wie finden die Zwischenergebnisse nun am sinnvollsten ihren Weg zu dem Programmteil, der mit ihnen weiterrechnet? `
Off the top of my head:Funktion A erstellt ein Array von "Aufgaben" und ein leeres Array, in dem später die "Ergebnisse" gespeichert werden.
Funktion B erwartet ebendieses Array von Aufgaben und das Ergebnis-Array:
-
Wenn beide gleich lang sind, ruft sie Funktion C mit den Ergebnissen auf
-
Wenn nicht, ruft sie getHistory mit der nächsten "Aufgabe" (Aufgabe[Ergebnisarray.length]) auf. Der Callback fügt das Ergebnis ans Ergebnisarray an und ruft dann Funktion B wieder auf.
Funktion C macht dann mit den Ergebnissen weiter.
–
Oder du verwendest TypeScript/Babel und erledigst alles in einer Async-Funktion, dazu muss aber ein Promise-Wrapper für getHistory geschrieben werden:
-
Objekt-ID-Array in ein Array von Promises für die getHistory-Rückgabewerte transformieren
-
var ergebnisse = await Promise.all(promiseArray);
-
mit den Ergebnissen weiterrechnen.
-
-
Muss ich für jedes Zwischenergebnis einen State anlegen? `
Muss/sollte kein State sein, Variablen tun es auch.Kann der Datentyp eines States auch ein Objekt sein? `
Jein. Du kannst per JSON.stringify ein Objekt in einen String wandeln und diesen speichern. Später dann per JSON.parse wieder zurückwandeln. Ist aber nicht besonders elegant.Kann die weiterführende Funktion auch auf die Aktualisierung mehrerer States warten, bevor sie ausgelöst wird? `
Nicht ohne weiteres, würde wie gesagt keine States verwenden für ZwischenergebnisseOder könnte ich auch für jedes Zwischenergebnis eine globale Variable in meinem Script anlegen und diese by-Reference übergeben? `
Objekte werden prinzipiell by ref übergeben, boolean, string, number, etc. by value. Warum muss es byRef sein?Von "zu vielen" globalen Variablen ist abzuraten, ist aber möglich. Mit sauber definierten Funktionen und -argumenten ist das nicht unbedingt nötig.
-
Zur Art und Weise wie JS Variablen übergibt habe ich noch was hübsches gefunden: https://stackoverflow.com/questions/660 … s-by-value
"Object Sharing" ist wohl der korrekte Fachbegriff und technisch gesehen fasst dieser Satz für mich alles zusammen:
"Javascript is always pass by value, but when a variable refers to an object (including arrays), the "value" is a reference to the object. "
Wenn ich das richtig verstanden habe, heißt das ich muss mir nur ein Objekt anlegen, und dieses meiner Funktion übergeben, die ihre Ergebnisse dort hineinschreibt. Danach stehen die Werte auch an der ursprünglichen Stelle zur Verfügung.
Hoffentlich finde ich am Wochenende die Zeit, das mal ausführlich auszuprobieren und dann etwas schlauer mit der nächsten Frage hier aufzuschlagen
Die Nebel lichten sich langsam - danke!