Karl Konrad Koreander: MutationObserver-Schleife stoppt vor dem letzten Element

Schönen Nachmittag,

Ich habe einen HTML-#Holder mit 5 IMGs. Ich habe einen MutationObserver, der die Attributveränderung von Elementen überwacht. Diese Elemente werden in einem ObserverVar-Objekt gesammelt, gleichzeitig wird El_Warteschleife um 1 erhöht. Wird eine Mutation erkannt, so kann der Observer des dazugehörigen Objekt wieder freigegeben werden (disconnect) und El_Warteschleife wird wieder um 1 vermindert.

...daher sollte am Ende des Tages El_Warteschleife bei 0 angelangt sein und console.log("NULL ANGELANGT!") triggern, das passiert aber nicht.

Zur Verdeutlichung logge ich auch das letzte Objekt mit console.log(obj), wodurch man schön sieht, dass die "naechsterSchritt"-Funktion VOR dem letzten Element stoppt.

Mein(e) Frage / Problem:

Warum "stoppt" meine "naechsterSchritt"-Funktion VOR dem letzten Element anstatt dass "NULL ANGELANGT!" freigegeben wird?

Danke!

let El_Warteschleife = 0;
let ObserverVar = {};
let ObjektZaehler = 0;

let ObsFunc = (obj) => {
	let naechsterSchritt = () => {
		console.log(obj); // letztes Objekt nicht mehr dabei!
		ObserverVar[ObjektZaehler].disconnect();
		El_Warteschleife--;
		if (El_Warteschleife === 0) {
			console.log("NULL ANGELANGT!");
		}
	};

	Object.keys(ObserverVar).length === 0 ? ObjektZaehler = 0 : ObjektZaehler++;		
	let config = {};
	config["attributes"] = true;
	let callback = () => {
		naechsterSchritt.call(null, ObserverVar);
	};
	ObserverVar[ObjektZaehler] = new MutationObserver(callback);
	ObserverVar[ObjektZaehler].observe(obj, config);

};

let LosGehts = (arg) => {
		El_Warteschleife++;
		ObsFunc(arg);
		arg.style["top"] = "12px";
};

let holder = document.getElementById("holder");
[...holder.children].forEach(el => {
	LosGehts(el);
});
  1. Hallo KKK,

    sorry, dieser Code ist so verquast, da finde ich mich überhaupt nicht mehr zurecht. Du verwendest eine Menge moderner Syntax-Elemente, wie Pfeilfunktionen und spread-Operatoren, aber bist auch sehr umständlich.

    [...holder.children].forEach(el => { LosGehts(el); });
    
    // einfacher:
    [...holder.children].forEach(LosGehts);
    

    oder

    let config = {};
    config["attributes"] = true;
    
    // einfacher:
    let config = { attributes: true };
    

    oder

    let callback = () => {
    		naechsterSchritt.call(null, ObserverVar);
    	};
    
    // einfacher:
    const callback = () => naechsterSchritt(ObserverVar);
    
    // noch einfacher: direkt naechsterSchritt als Callback setzen, 
    // denn ObserverVar ist ohnehin global.
    

    Ich bin auch nicht sicher, dass in diesem Callback-Gestrüpp die Ausführungsreihenfolge so ist, wie Du Dir das vorstellst, inbesondere durch Verwendung der globalen Variablen ObjektZaehler. Und ich kapiere auch nicht, warum Du ObjektZaehler und El_Warteschleife verwendest; das ist doch beides ein Index für die gespeicherten Observer. Vermutlich bist Du da irgendwo um 1 daneben, das könnte man durch Loggen herausbekommen.

    Aber ich halte das für Zeitvergeudung. Dein Code ist viel zu komplex. Du möchtest doch

    • dass für jedes der 5 Bilder ein MutationObserver installiert wird
    • dieser MutationObserver bei Ändern irgendeines Attributs aktiv wird und irgendwas tut (das irgendwas hast Du uns aber nicht gezeigt, ok)
    • und sich der Observer danach disconnected.

    Das geht viel eleganter. Du rufst ja für jedes Bild die LosGehts Funktion auf. Diese erzeugt den Observer und enthält den Callback. Den Callback klebst Du an den Observer, und der Observer klebt sich dann ans Bild.

    Wichtig: Am Callback klebt der Aufrufkontext der LosGehts Funktion, in der dieser Observer erzeugt wurde. Und damit auch das Observer-Objekt, wenn Du es dort in einer lokalen Variablen speicherst. Und damit könntest Du dann hantieren. Eine globale Registry ist überhaupt nicht nötig.

    Das zeige ich aber gar nicht erst, denn es geht hier noch einfacher. Der Callback bekommt vom Observer zwei Parameter: den MutationRecord, und den Observer selbst. Und Bob ist dein Onkel.

    const observe = elem => {
       // Zeilenumbrüche für bessere Lesbarkeit im Forum
       new MutationObserver( (record, obs) => {
             // tu was mit dem MutationRecord
             // ...
             // und klemme den Observer wieder ab
             obs.disconnect();
       })
       .observe(
          elem,
          { attributes: true }
       );
    };
    
    [...document.getElementById("holder").children].forEach(elem => {
       observe(elem);
       elem.style["top"] = "12px";
    });
    

    Dieser Code ist jetzt ungetestet, aber ich würde behaupten, er passt zu dem, was Du eigentlich willst. Die observe-Funktion könnte man auch noch inline in den foreach kleben, aber eigentlich ist das JavaScript und nicht APL (die Sprache, für die man eine eigene Tastatur braucht und in der man einzeilige Programme beliebiger Komplexität schreiben kann).

    Gruß auch an Bastian Balthasar Bux
    Rolf

    --
    sumpsi - posui - obstruxi
    1. Du möchtest doch

      • dass für jedes der 5 Bilder ein MutationObserver installiert wird
      • dieser MutationObserver bei Ändern irgendeines Attributs aktiv wird und irgendwas tut (das irgendwas hast Du uns aber nicht gezeigt, ok)
      • und sich der Observer danach disconnected.

      Nein.

      Ich möchte, dass etwas passiert, wenn die MutationObserver aller Bilder ausgelöst haben, i.e. die Attribute aller Bilder verändert worden sind. Habe ich relativ klar kommuniziert.

      ...vielen lieben Dank für die Grüße an Bastian, der wurde nur leider vom letzten MutationObserver gefressen, der verrückt spielt

      1. Hallo Karl,

        okay, deine Null-Ausgabe hatte ich für reinen Debug gehalten. Ich habe übrigens mal Logs in Deinen eigenen Code eingebaut, der Fehler ist, dass Du in naechsterSchritt immer den Observer Nr. 4 disconnectest und darum die Attributänderung für das letzte Bild nicht mehr mitbekommst. Globale Variablen funktionieren hier nicht gut.

        Statt meinen Code einfach zurückzuweisen, könntest Du ihn auch nutzen. Man muss nur ein bisschen hinzufügen.

        let openObservations = 0;
        
        const handleComplete = () => {
           console.log("Alle Bilder positioniert");
        };
        
        const observe = elem => {
           openObservations++;
           new MutationObserver( (record, obs) => {
                 obs.disconnect();
                 if (--openObservations == 0)
                    handleComplete();
           })
           .observe(elem, { attributes: true });
        };
        
        [...document.getElementById("holder").children].forEach(elem => {
           observe(elem);
           elem.style["top"] = "12px";
        });
        

        Das ist aber immer noch ein globaler Zähler und eigentlich unschön. Wir können auch Promises an den Start bringen. Je Bild ein Promise, der Observer resolved das Promise, und über Promise.all führen wir es zusammen. Ich würde dann nur erst alle Observer erzeugen und das Promise scharf schalten, bevor ich die Bilder repositioniere.

        const observePromise = bild => {
           return new Promise( (resolve, reject) => {
              new MutationObserver( (rec, obs) => {
                 obs.disconnect();
                 resolve(bild);
              })
              .observe(bild, { attributes: true });
           });
        };
        
        const bilder = [...document.getElementById("holder").children];
        const observations = bilder.map( bild => observePromise(bild));
        
        Promise.all(observations)
        .then( results => {
           console.log("Alle Bilder sind positioniert");
        });
        
        bilder.forEach(bild => {
           bild.style = "margin-top: 100px";
        });
        

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Danke für deine Hilfestellung - sehe aber den Wald mittlerweile vor Bäumen nicht mehr - werde mir das morgen ansehen 👍

          1. Hallo Karl,

            vielleicht erzählst DU auch mal, was Du eigentlich erreichen willst. Warum musst Du warten, bis eine Menge von Bildern positioniert ist?

            Welches Problem willst Du lösen? Vielleicht geht es ja auch grundsätzlich einfacher.

            Rolf

            --
            sumpsi - posui - obstruxi
  2. Hallo Karl,

    ein Nachtrag. Ich nehme an, du hast diese globale Observer-Registry gebaut, damit der Garbage Collector nicht die Observer abräumt. Das ist aber nicht nötig. Mit dem observe-Aufruf hängt sich der Observer an das DOM Element und hat damit eine Sicherheitsleine, die ihn vor dem Collector schützt. Nach dem disconnect-Aufruf ist die Leine weg und der Garbage Collector kann den Observer abräumen.

    Rolf

    --
    sumpsi - posui - obstruxi
  3. Tach!

    Warum "stoppt" meine "naechsterSchritt"-Funktion VOR dem letzten Element anstatt dass "NULL ANGELANGT!" freigegeben wird?

    Abgesehen von den allgemeinen Anmerkungen zum Code von Rolf B, der Weg diese Frage zu beantworten heißt Debugging. Dafür bieten die Browser sehr gute Möglichkeiten in den eingebauten Entwickler-Tools. Neben dem Debugger, der Breakpoints und schrittweises Ausführen gestattet, gibt es auch noch die Möglichkeit mit console.log() seinen Code zu spicken, um so die Dinge nachzuvollziehen.

    dedlfix.