914 Stimmen

Warum bleibt meine Variable unverändert, nachdem ich sie innerhalb einer Funktion modifiziere? - Asynchroner Code-Verweis

Bei den folgenden Beispielen, warum ist outerScopeVar in allen Fällen undefiniert?

var outerScopeVar;

var img = document.createElement('img');
img.onload = function() {
    outerScopeVar = this.width;
};
img.src = 'lolcat.png';
alert(outerScopeVar);

var outerScopeVar;
setTimeout(function() {
    outerScopeVar = 'Hallo asynchrone Welt!';
}, 0);
alert(outerScopeVar);

// Beispiel mit jQuery
var outerScopeVar;
$.post('loldog', function(response) {
    outerScopeVar = response;
});
alert(outerScopeVar);

// Node.js Beispiel
var outerScopeVar;
fs.readFile('./catdog.html', function(err, data) {
    outerScopeVar = data;
});
console.log(outerScopeVar);

// mit Promises
var outerScopeVar;
myPromise.then(function (response) {
    outerScopeVar = response;
});
console.log(outerScopeVar);

// mit Observables
var outerScopeVar;
myObservable.subscribe(function (value) {
    outerScopeVar = value;
});
console.log(outerScopeVar);

// Geolocation-API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
    outerScopeVar = pos;
});
console.log(outerScopeVar);

Warum gibt es in all diesen Beispielen undefined aus? Ich möchte keine Workarounds, ich möchte wissen warum dies passiert.


Hinweis: Dies ist eine Kanonische Frage zur JavaScript-Asynchronität. Fühlen Sie sich frei, diese Frage zu verbessern und weitere vereinfachte Beispiele hinzuzufügen, mit denen die Community identifizieren kann.

1voto

smabrar Punkte 534

Die kurze Antwort lautet: Asynchronität.

Warum ist Asynchronität erforderlich?

JavaScript ist single-threaded, was bedeutet, dass zwei Teile des Skripts nicht gleichzeitig ausgeführt werden können; sie müssen nacheinander ausgeführt werden. In Browsern teilt JavaScript einen Thread mit einer Vielzahl anderer Dinge, die je nach Browser variieren. Typischerweise befindet sich JavaScript jedoch in derselben Warteschlange wie das Malen, Aktualisieren von Styles und Bearbeiten von Benutzeraktionen (wie das Hervorheben von Text und die Interaktion mit Formularsteuerelementen). Eine Aktivität in einem dieser Bereiche verzögert die anderen.

Sie haben wahrscheinlich Ereignisse und Rückrufe verwendet, um das zu umgehen. Hier sind Ereignisse:

var img1 = document.querySelector('.img-1');

img1.addEventListener('load', function() {
  // Bild geladen
  console.log("Geladen");
});

img1.addEventListener('error', function() {
  // Fehler erkannt
  console.log("Fehler gedruckt");
});

Dies ist überhaupt nicht nieselig. Wir erhalten das Bild, fügen ein paar Zuhörer hinzu, und dann kann JavaScript aufhören zu arbeiten, bis einer dieser Zuhörer aufgerufen wird.

Leider ist es im obigen Beispiel möglich, dass die Ereignisse eingetreten sind, bevor wir begonnen haben, auf sie zu hören, daher müssen wir dies umgehen, indem wir die "complete"-Eigenschaft von Bildern verwenden:

var img1 = document.querySelector('.img-1');

function loaded() {
  // Bild geladen
  console.log("Geladen");
}

if (img1.complete) {
  loaded();
} else {
  img1.addEventListener('load', loaded);
}

img1.addEventListener('error', function() {
  // Fehler erkannt
  console.log("Fehler gedruckt");
});

Dies fängt keine Bilder ab, die Fehler verursacht haben, bevor wir die Gelegenheit hatten, auf sie zu hören; leider gibt uns das DOM keine Möglichkeit, das zu tun. Außerdem lädt dies ein Bild. Es wird noch komplizierter, wenn wir wissen wollen, wann eine Gruppe von Bildern geladen wurde.

Ereignisse sind nicht immer der beste Weg

Ereignisse eignen sich gut für Dinge, die mehrmals am selben Objekt passieren können - keyup, touchstart usw. Bei diesen Ereignissen kümmert es Sie nicht wirklich, was passiert ist, bevor Sie den Zuhörer angefügt haben.

Die beiden Hauptmethoden, um es richtig zu machen, sind Rückrufe und Versprechungen.

Rückrufe

Rückrufe sind Funktionen, die als Argumente an andere Funktionen übergeben werden, dieser Vorgang ist in JavaScript gültig, da Funktionen Objekte sind und Objekte als Argumente an Funktionen übergeben werden können. Die grundlegende Struktur der Rückruffunktion sieht in etwa so aus:

function getMessage(callback) {
  callback();
}

function showMessage() {
  console.log("Hallo Welt! Ich bin ein Rückruf");
}
getMessage(showMessage);

Versprechen

Obwohl es Möglichkeiten gibt, die Callback-Hölle mit Vanilla JS zu umgehen, werden Versprechungen immer beliebter und werden derzeit in ES6 standardisiert (siehe Versprechen).

Ein Versprechen ist ein Platzhalter, der das spätere Ergebnis (Wert) einer asynchronen Operation darstellt

  • der Versprechen-Platzhalter wird durch den Ergebniswert ersetzt (bei Erfolg) oder den Grund für das Scheitern (bei Misserfolg)

Wenn Sie nicht wissen müssen, wann etwas passiert ist, sondern nur ob es passiert ist oder nicht, dann ist ein Versprechen das, wonach Sie suchen.

Ein Versprechen ist ein wenig wie ein Ereignislistener, mit dem Unterschied, dass:

  • ein Versprechen nur einmal erfolgreich oder erfolglos sein kann
  • ein Versprechen kann nicht von Misserfolg auf Erfolg umschalten oder umgekehrt
  • nachdem Sie ein Ergebnis haben, bleibt das Versprechen unveränderlich
  • wenn ein Versprechen erfolgreich oder erfolglos war und Sie später einen Erfolgs-/Misserfolgsrückruf hinzufügen, wird der richtige Rückruf aufgerufen
  • es spielt keine Rolle, dass das Ereignis vor dem Hinzufügen des Rückrufs aufgetreten ist

Hinweis: Geben Sie immer ein Ergebnis aus einer Funktion innerhalb eines Versprechens zurück, sonst gibt es nichts, auf das die nachfolgende Funktion reagieren kann.

Versprechen Terminologie

Ein Versprechen kann sein:

  • erfüllt: Die mit dem Versprechen verbundene Aktion war erfolgreich
    • die asynchrone Operation wurde abgeschlossen
    • das Versprechen hat einen Wert
    • das Versprechen wird sich nicht mehr ändern
  • abgelehnt: Die mit dem Versprechen verbundene Aktion ist fehlgeschlagen
    • die asynchrone Operation ist gescheitert
    • das Versprechen wird nie erfüllt sein
    • das Versprechen hat einen Grund, der angibt, warum die Operation fehlgeschlagen ist
    • das Versprechen wird sich nicht mehr ändern
  • ausstehend: Noch nicht erfüllt oder abgelehnt
    • die asynchrone Operation wurde noch nicht abgeschlossen
    • kann zu erfüllt oder abgelehnt übergehen
  • festgelegt: Wurde erfüllt oder abgelehnt und ist somit unveränderlich

Wie man ein Versprechen erstellt

function getMessage() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('Hallo Welt! Ich bin ein Versprechen');
    }, 0);
  });
}

getMessage().then(function(message) {
  console.log(message);
});

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X