dynamische Module node
effel
- javascript
Hallo,
es ist mir hier schon viel geholfen worden, nun ein neues Problem: ich habe in node ein Programmpaket von ca 12000 Zeilen und da alles miteinander verflochten ist - eine sehr aufwendige Testarbeit. Deshalb jetzt mit Modulen, und zwar mit dynamischen, denn ich möchte Module austauschen ohne alles immer wieder neu zu starten. mit require ist ok, aber das sind statische Module.
Info: das Modul liegt in ./dr_konstruk2, dort die function dr_konstruk2, der Modulname (bei require) ist p_dr_konstruk2 die vielen Argumente sind function-Adressen im "Hauptprogramm" Es hat alles in require funktioniert
Jetzt mit import: 1.Versuch:
function dr_konstruk2yy(arg1){
p_dr_konstruk2=import('./dr_konstruk2.js')
.then(p_dr_konstruk2.dr_konstruk2(arg1,
ADA,dr_lesen,vergl,dr_x_y_norm,arraino,dr_append,div,sub,abs))
}
Fehler(TypeError: Cannot read properties of undefined (reading 'dr_konstruk2')
function dr_konstruk2(arg1) {
import('./dr_konstruk2.js')
.then(dr_konstruk2(arg1,ADA,dr_lesen,vergl,dr_x_y_norm,
arraino,dr_append,div,sub,abs))
}
Fehler
async function dr_konstruk2XX(arg1) {
p_dr_konstruk2 = await import('./dr_konstruk2.js');
p_dr_konstruk2.dr_konstruk2(arg1,ADA,dr_lesen,vergl,dr_x_y_norm,
arraino,dr_append,div,sub,abs)
}
Das lief durch, aber das Programm wurde nicht beendet ??
Wer kann helfen und mich aufklären?
Danke Effel
Hallo effel,
leider ist meine Überarbeitung von Promises im Wiki noch nicht fertig, aber vielleicht hilft Dir der aktuelle Stand auch schon weiter.
Kurz gesagt: Die import-Funktion gibt ein Promise zurück, das mit dem Modul-Objekt erfüllt wird. Dieses Objekt wird dann an die Funktion übergeben, die .then() als Parameter erhält.
Das Problem: .then() wird aufgerufen, bevor das Promise erfüllt ist. Wenn Du also schreibst:
let modul = import("./mymodule.js")
.then(modul.f1(a,b,c));
dann gibt es hier gleich mehrere Probleme.
(1) An modul
wird nicht das Modul zugewiesen, sondern ein Promise. Und zwar nicht das, was von import() zurückkommt, sondern das, das von then() zurückkommt.
(2) Wenn then() so funktionierte, wie Du Dir das vorstellst, dann würdest Du an then() nicht die Funktion im Modul übergeben, sondern ihr Ergebnis. Das ist typischerweise keine Funktion. Das ist der zweitwichtigste Grund, warum das nicht funktioniert
(3) Der wichtigste Grund ist aber: then() wird auf dem Promise aufgerufen, das import zurückgibt, und zwar sofort, nachdem der import-Request abgesetzt ist. In modul
befindest sich das Promise, das aus import zurückkam, nicht das Modul, und dieses Promise ist in dem Moment definitiv noch unerfüllt. Also selbst wenn in modul
das Modul stünde, wäre das Modul jetzt noch nicht verfügbar.
Dein zweiter Versuch kann eigentlich gar nicht klappen, weil Du an then() das Ergebnis von dr_konstruk2 übergeben willst. In dieser Funktion befindest Du Dich gerade, d.h. das ist eigentlich eine Endlos-Rekursion, die mit einem Stacküberlauf enden müsste.
Dein dritter Versuch verwendet await und versteckt damit das Promise vor Dir. Jetzt bekommst Du in p_dr_konstruk2 korrekt das Modul und solltest auch die vom Modul exportierte Funktion dr_konstruk2 aufrufen können. Wenn das Programm nicht endet, dann ist node der Meinung, dass da noch Arbeit offen ist. Das kann viele Gründe haben, dafür müsste ich jetzt selbst mit node und Promises Versuche machen. Was ich mangels verfügbarem Gerät in dieser Woche nicht kann. Ich weiß nicht, ob Node so lange weiterläuft, wie es Promises im Zustand "pending" gibt. Hast Du mal mit console.log im Programm Marker ausgegeben, wo er gerade ist?
Um den Ablauf nochmal zu verdeutlichen: import() schickt den Laderequest für das Modul ab und liefert ein Promise. Dieses wird erfüllt, sobald das Modul da ist. Wenn Du das mit .then machen willst, dann so:
import("./mymodule.js")
.then(function(mymodule) {
console.log("2. Import ist fertig");
mymodule.myFunction(x,y,z,a,b,c);
tuMehr();
});
console.log("1. Import ist bestellt");
tuwas();
Was hier wichtig zu wissen ist: Die Funktion, die .then übergeben wird, wird nicht in dem Moment aufgerufen, wo .then() läuft. Sie wird lediglich erstellt und an .then übergeben. Der Aufruf erfolgt im Hintergrund (als "Mikrotask"), wenn das Promise sich erfüllt. Die beiden console.log-Aufrufe im Beispiel sind nicht falschrum nummeriert, er loggt zuerst "bestellt", führt dann tuwas() aus und danach kommt erst "fertig". Heißt auch: der tuwas()-Aufruf kann noch nicht auf die Ergebnisse von myFunction() zugreifen. Weil die noch gar nicht gelaufen ist. Der tuMehr()-Aufruf hingegen findest nach dem Aufruf von myFunction statt und kann damit deren Ergebnisse nutzen.
Ein JavaScript-Programm läuft auch unter Node in einer Eventloop, d.h. das Hauptprogramm läuft durch und hinterlässt dabei Eventhandler oder Callbacks, die das Ergebnis von asynchronen Aktivitäten verarbeiten. Solange das Hauptprogramm nicht zu Ende ist, kann der then-Callback nicht laufen.
Mit await versteckst Du diesen Mechanismus. Was in einer async-Funktion hinter await folgt, wird von JavaScript automatisch in eine Callback-Funktion für then() verpackt. Deshalb ist await auch nur in einer async-Funktion erlaubt (und auf dem Top-Level von ECMAScript-Modulen). Eine async-Funktion endet beim ersten await und gibt ein Promise zurück, das durch den Rückgabewert der Funktion erfüllt wird. Der Rest der Funktion wird ausgeführt, wenn das Promise, das Du awaitest, erfüllt wird.
Das heißt aber auch dies: Wenn Du dieses Programm laufen lässt:
let x = 3;
import("./mymodule.js")
.then(function(mymodule) {
mymodule.myFunction(x);
});
x = 5;
dann wird myFunction mit dem Wert 5 aufgerufen. NICHT mit dem Wert 3. Die Callback-Funktion für .then schließt die Variable x in ihre Closure ein und bewahrt die Variable damit für den Moment auf, wo der Callback ausgeführt wird, aber die Closure enthält einen Verweis auf die Variable, nicht ihren eingefrorenen Wert. Deshalb sieht die Funktion den letzten Wert, den x hatte, und nicht den, der im Moment des then-Aufrufs vorlag.
Promises sind nicht ganz einfach. Um Dir genauere Tipps zu geben, kennen wir zu wenig von deiner Software.
Was auch noch zu beachten ist: was Du mit import() lädst, bleibt geladen. Du kannst also mittels import() dynamisch entscheiden, welche Module Du brauchst, aber du kannst kein Modul entladen, das Du nicht mehr brauchst. Ich meine aber auch, dass ein zweimaliger Import des gleichen Moduls kein Problem ist. Der zweite Import liefert Dir genauso ein Promise wie der erste, aber er lädt den Code nicht nochmal, d.h. das Promise erfüllt sich schneller. Der .then-Aufruf erfolgt aber ebenfalls asynchron.
Rolf
erstmal Danke,
jetzt habe ich viel zum Kauen
Effel
Danke Rolf
habe viel studiert und einiges gelernt. Sehr gute Beschreibung, geht in alle Tiefen
so meine Erkenntnis:
Yours:
import("./mymodule.js")
.then(function(mymodule) {
console.log("2. Import ist fertig");
mymodule.myFunction(x,y,z,a,b,c);
tuMehr();
});
console.log("1. Import ist bestellt");
tuwas();
Meins:
function dr_konstruk2yy(arg1){//function innerhalb des "Hauptprogramms"
import('./dr_konstruk2.js') //erzeugt zunächst ein Promise, das then übergeben wird
.then(function(dr_konstruk2) {//then startet die Callbackfunction, nachdem das Module geladen
//unklar:function(dr_konstruk2),warum nicht gleich
//p_dr_konstruk2.dr_konstruk2(....
console.log("2.Import ist fertig"); // nur zur Info -oder ?
p_dr_konstruk2.dr_konstruk2(arg1, //das, was ich will
ADA,dr_lesen,vergl,dr_x_y_norm,arraino,dr_append,div,sub,abs);
})
console.log("modul dr_konstruk2 wird geladen");//das Programm hält nicht an, sondern läuft
//weiter - durch console.log zu nächst ein Stopp bei node
}
was Du mit import() lädst, bleibt geladen. schade - das wollte ich
async - await - der Fehler liegt irgend woanders nicht in diesem Problem
Hallo effel,
du hast den Mechanismus vermutlich noch nicht ganz verstanden, das schließe ich aus deiner Unklarheit.
Ich versuche mal, die ganzen Zwischenwerte, mit denen in den bisherigen Beispielen heimlich operiert wurde, in Variablen zwischenzuspeichern um sie sichtbarer zu machen.
Ein Hinweis noch: Setze vor Beispielcode eine Zeile ~~~js
und dahinter eine Zeile mit ~~~
, dann wird das Beispiel ordentlich als JavaScript formatiert.
Also:
let modulPromise, thenPromise;
modulPromise = import('./dr_konstruk2.js');
function erfolgsCallback(modul) {
modul.dr_konstruk2(arg1, ADA, dr_lesen, vergl, dr_x_y_norm,
arraino, dr_append, div, sub, abs);
}
thenPromise = modulPromise.then(erfolgsCallback);
Von import() bekommst Du ein Promise-Objekt für das Modul. Dieses Promise hat eine Methode then, und dieser then-Methode übergibst Du eine Funktion, die im Erfolgsfall aufzurufen ist und der das importierte Dings übergeben wird.
Dieses Dings ist ein "Modul-Namespace" Objekt. Es ist ein Objekt, das all das enthält, was vom importierten Modul exportiert wird. D.h. wenn Du on dr_konstruk2.js stehen hast
export function dr_konstruk2(a,b,c,d) { ... }
dann findest Du in modul.dr_konstruk2 die exportierte Funktion. Ob das Namespace-Objekt nun
modul
heißt oderkonstrukt
oderhans
, ist natürlich wurscht.
Dieses Namespace-Objekt ist aber erst beim Aufruf der Callback-Funktion verfügbar, die Du an then
übergibst, und auch nur von ihr. Ganz wichtig: Du übergibst die Funktion, nicht den Wert ihres Aufrufs. Du schreibst .then(erfolgsCallback)
, da ist kein Klammerpaar hinter dem Funktionsnamen. Eine JavaScript-Funktion ist selbst ein Objekt, und genau dieses Objekt wird an then übergeben. Den Aufruf übernimmt then, sobald das Modul da ist. Und deswegen ist .then(p_dr_konstruk2.dr_konstruk2(a,b,c,d))
sinnlos, weil Du JavaScript damit anweist, die Methode dr_konstruk2
des Objekts p_dr_konstruk2
aufzurufen und deren Rückgabewert an then
zu übergeben. Ich glaube nicht, dass Du das willst. Man kann solche Konstrukte zwar errichten, aber dann müsste die Methode bereits vor dem Import verfügbar sein und die Methode müsste eine Funktion höherer Ordnung sein, d.h. eine Funktion, die eine andere Funktion als Rückgabewert hat. Kann man machen, ja, aber das ist das JavaScript-Gegenstück zur höheren Mathematik.
Zum Abschluss noch etwas zu dieser Variablen thenPromise
: then gibst seinerseits ein Promise zurück. Sein Wert wird festgelegt, wenn die Callback-Funktion endet, die Du an then übergeben hast. Ob es erfüllt oder zurückgewiesen wird, hängt vom Rückgabewert des Callbacks ab. Du kannst auf diese Weise asynchrone Vorgänge zu einer Kette verknüpfen. In dem Wiki-Artikel, den ich verlinkte, steht was dazu, es ist aber schon eine komplexere Anwendung von Promises.
Sinnvoll ist hingegen sowas:
import('./modul.js')
.then(function(modul) {
... modul verwenden
})
.catch(function(error) {
... Fehler protokollieren
});
Die catch-Methode legt einen Callback für den Fall fest, dass ein Promise nicht erfüllt wird, sondern scheitert. Wenn man das so wie gezeigt schreibt, dann kann dies das Scheitern von import() sein (modul.js existiert nicht oder enthält einen Syntaxfehler) oder auch das Scheitern des then-Callbacks. In beiden Fällen läuft er in den catch-Callback und Du kannst den Fehler protokollieren.
Rolf
ok, Rolf,
es hat geklappt. -danke
noch eine letzte Frage. es entstehen ja 2 Treads - oder ? gibt es eine Möglichkeit, beide wieder zusammen zu bringen? auch bei read und write hatte ich das und habe durch console.log einen Stopp eingelegt und dann weiter..
Hallo effel,
Nein, das sind keine Threads. Es sind asynchrone Abläufe, aber die Ausführung erfolgt hintereinander auf dem gleichen Thread.
Du kannst das mit Promises lösen, aber ich habe noch nicht genau verstanden, was du womit synchronisieren willst.
Ich stelle mir das gerade so vor. Dein Ablauf ist – abstrakt – dieser:
func1();
func2();
func3();
und du hast func2 in ein Modul ausgelagert. Es gibt vielleicht func2a, func2b und func2c, jede in einem eigenen Modul, und du lädst mit import() das Modul, das gerade erforderlich ist.
func3() soll aber erst passieren, wenn die func2-Inkarnation fertig ist?
Rolf
ja, genau so
Effel
Hallo effel,
okay. Es gibt zwei Ansätze.
Erstens: Alles, was hinter func2() passieren muss, kommt mit in die Callback-Funktion, die Du an then übergibst.
func1();
import("./func2a.js")
.then(function(modul2) {
modul2.func2a();
func3();
}
Nachteilig ist hier, dass Du einiges an Code doppeln müsstest. Wenn Du für func2() mehrere Varianten hast, dann kopierst Du func3() - oder vielleicht auch noch mehr Code - in jede der Callback-Funktionen.
Aber ich schrieb ja, dass then selbst ein Promise zurückgibt. Es erfüllt sich, wenn der then-Callback fertig ist. Und wenn Du Code dieser Art hast, kannst Du dieses Promise nutzen. Du kannst in jedem Zweig dieses Promise speichern - immer in der gleichen Variablem - und dann den Rest von der Erfüllung dieses Promises abhängig machen:
func1();
let func2Promise;
if (...) {
func2Promise =
import("./func2a.js")
.then(function(modula) { modula.func2a(); }
else if (...) {
func2Promise =
import("./func2b.js")
.then(function(modulb) { modulb.func2b(); }
}
func2Promise.then(function() {
func3();
}
Rein hypothetisch kannst Du, wenn Du mehrere unabhängige Codeteile hast, auch mehrere Importe auslösen, für jeden einen .then-Handler vorsehen und vom jedem then-Handler das Promise speichern. Und dann verwendest Du Promise.all(), um den Moment abzuwarten, wo sich alle diese Promises erfüllen. Grob skizziert sieht das so aus:
const thenA =
import("./func_a.js")
.then(function(modulA) { modulA.funcA(); }
const thenB =
import("./func_b.js")
.then(function(modulB) { return modulB.funcB(); }
const thenC =
import("./func_c.js")
.then(function(modulC) { return modulC.funcC(); }
Promise.all( [ thenA, thenB, thenC ])
.then(function(ergebnis) {
... code, der laufen soll, wenn funcA, funcB und und funcC fertig sind
}
Du kannst in den Callback-Funktionen von then einen Wert zurückgeben. Dieser Rückgabewert ist dann der Wert des von then gelieferten Promises und wird an den Callback für DESSEN then verwendet.
Im Falle von Promise.all ist es so, dass Du ein Array von Promises übergibst und Promise.all darauf wartet, dass sich alle erfüllen. Die Ergebniswerte dieser Promises werden in einem Array gesammelt und an den then-Handler übergeben.
D.h. der then von Promise.all bekommt ein Array mit 3 Einträgen.
Index 0: Rückgabewert des then-Callbacks von Modul A. Da dort kein return steht, ist das undefined
Index 1: Rückgabewert des then-Callbacks von Modul B. Dort steht ein return, d.h. du findest auf Index 1 den Rückgabewert von funcB().
Index 2: Das gleiche, für Modul C.
In welcher Reihenfolge funcA, funcB und funcC ablaufen, weißt Du nicht. Das hängt davon ab, wie schnell die Module geladen werden konnten. Sie laufen definitiv nicht gleichzeitig, sondern nacheinander - nur die Reihenfolge ist undefiniert. Gleichzeitig erfolgt aber - soweit möglich - das Laden des Programmcodes für diese Module, denn das Betriebssystem kann sowas parallelisieren. Deshalb kann man sowas nur machen, wenn diese Funktionen voneinander unabhängig sind. Aber über das Promise.all bekommst Du einen Synchronisierungspunkt, an dem Du weißt, dass alle drei fertig sind, und kannst über den Rückgabewert des then-Callbacks auch Daten einsammeln. Dieser Rückgabewert kann ja durchaus komplex sein: ein Objekt, ein Array, ein Array aus Objekten - das kann riesig werden.
Man kann mit Promises komplexe Ablaufszenarien konstruieren. Wenn man weiß, wie die Dinger ticken...
Rolf
Danke, ich werde es studieren Effel
Lieber Rolf,
es war ja interessant und lehrreich in die dynam.Module einzusteigen - ich bedanke mich - und wende mich aber jetzt anderen Themen zu
effel