asynchrones JavaScript
Gunnar Bittersmann
- javascript
3 dedlfix0 Felix Riesterer0 pl0 Tote Locke0 JürgenB
Folgende Situation: Zwei JSON-Dateien, die geladen werden müssen, und Code, der ausgeführt werden soll, wenn beide geladen sind. Verschachtelt sähe das so aus:
var aRequest = new Request('a.json');
var bRequest = new Request('b.json');
fetch(aRequest)
.then(function (response) { return response.json(); })
.then(function (aData)
{
fetch(bRequest)
.then(function (response) { return response.json(); })
.then(function (bData)
{
// do something with aData and bData
});
});
Ist aber nicht richtig asynchron. Die Dateien sollten unabhängig voneinander parallel geladen werden:
var aRequest = new Request('a.json');
var bRequest = new Request('b.json');
fetch(aRequest)
.then(function (response) { return response.json(); })
.then(function (aData)
{
// …
});
fetch(bRequest)
.then(function (response) { return response.json(); })
.then(function (bData)
{
// …
});
function doSomething(aData, bData)
{
// do something with aData and bData
}
Aber was müsste dann anstelle von // …
stehen, damit doSomething(aData, bData)
aufgerufen wird, sobald beide JSON-Dateien geladen sind?
LLAP 🖖
Tach!
Ist aber nicht richtig asynchron. Die Dateien sollten unabhängig voneinander parallel geladen werden:
Es sieht mir nicht so aus, als ob das mit fetch() allein gelöst werden kann. Promise.all() wird wohl der Lösungsweg sein.
dedlfix.
@@dedlfix
Es sieht mir nicht so aus, als ob das mit fetch() allein gelöst werden kann. Promise.all() wird wohl der Lösungsweg sein.
Danke, funzt:
var aRequest = new Request('a.json');
var bRequest = new Request('b.json');
var aPromise = fetch(aRequest).then(function (response) { return response.json(); })
var bPromise = fetch(bRequest).then(function (response) { return response.json(); })
Promise.all([aPromise, bPromise]).then(function (values)
{
var aData = values[0];
var bData = values[1];
// do something with aData and bData
});
LLAP 🖖
Tach!
var aRequest = new Request('a.json'); var bRequest = new Request('b.json'); var aPromise = fetch(aRequest).then(function (response) { return response.json(); }) var bPromise = fetch(bRequest).then(function (response) { return response.json(); }) Promise.all([aPromise, bPromise]).then(function (values) { var aData = values[0]; var bData = values[1]; // do something with aData and bData });
Du kannst auch noch die beiden ersten then()s einsparen. DRY statt WET. response.json() ist kein asynchroner Prozess. Das kann auch im then() vom Promise.all() ausgeführt werden.
Promise.all([aPromise, bPromise]).then(function (responses) {
var aData = responses[0].json();
var bData = responses[1].json();
// do something with aData and bData
});
oder wenn es separat sein soll
Promise.all([aPromise, bPromise]).then(function (responses) {
var values = responses.map(function (response) { return response.json(); });
// modern: const values = responses.map(response => response.json());
// oder: const [aValue, bValue] = responses.map(response => response.json());
var aData = values[0];
var bData = values[1];
// do something with aData and bData
});
dedlfix.
@@dedlfix
Du kannst auch noch die beiden ersten then()s einsparen.
Eher nach woanders verlagern.
response.json() ist kein asynchroner Prozess.
Hm, das heißt, das Parsen der JSON-Ressourcen kann nicht parallel erfolgen?
Mein Gedankengang war: Angenommen, das Laden von a.json dauert 5 Sekunden, das Parsen 0.1 Sekunden. Das Laden von b.json dauert 0.1 Sekunden, das Parsen 5 Sekunden[1]. Dann wären beide Promises gleichzeitig erfüllt und der Nutzer bekommt schnellstmöglich das Ergebnis zu sehen.
Das kann auch im then() vom Promise.all() ausgeführt werden.
Dann würde das Parsen von b.json erst nach dem Laden von a.json beginnen; der Nutzer müsste 4.9 Sekunden länger warten. Wenn mein Gedankengang denn stimmt.
LLAP 🖖
Das ist natürlich völlig aus der Luft gegriffen. Aber es soll ums Prinzip gehen. ↩︎
Hello,
Hm, das heißt, das Parsen der JSON-Ressourcen kann nicht parallel erfolgen?
siehe hierzu auch Artikel über Webworker
Liebe Grüße
Tom S.
Tach!
Du kannst auch noch die beiden ersten then()s einsparen.
Eher nach woanders verlagern.
response.json() ist kein asynchroner Prozess.
Hm, das heißt, das Parsen der JSON-Ressourcen kann nicht parallel erfolgen?
Parallel geht sowieso nichts im Single-Thread-Javascript.
Mein Gedankengang war: Angenommen, das Laden von a.json dauert 5 Sekunden, das Parsen 0.1 Sekunden. Das Laden von b.json dauert 0.1 Sekunden, das Parsen 5 Sekunden[^1]. Dann wären beide Promises gleichzeitig erfüllt und der Nutzer bekommt schnellstmöglich das Ergebnis zu sehen.
b wäre in dem Fall zuerst erfüllt, dessen Callback aufgerufen und das Parsen startet. Erst wenn das fertig ist, kann der Callback von a bearbeitet werden, dem dann sein Parsen folgt.
Eigentlich solltest du das Parsen aber nicht bemerken. So groß werden die Daten nicht sein, dass das ins Gewicht fällt. Und dann wäre der Übertragungsprozess auch noch ein viel größerer Flaschenhals.
Das kann auch im then() vom Promise.all() ausgeführt werden.
Dann würde das Parsen von b.json erst nach dem Laden von a.json beginnen; der Nutzer müsste 4.9 Sekunden länger warten. Wenn mein Gedankengang denn stimmt.
Ja. Die Idee ist gut, aber ich würde das zunächst in die Microoptimierungsschublade einsortieren, bis da was merkbares hochkommt.
Du kannst jedoch die zwei fetch() mit then() in ein requests.map() stecken, dann sparst du dir den doppelten Code und hast den zeitlichen Vorteil.
dedlfix.
Hallo!
response.json() ist kein asynchroner Prozess.
Doch, schließlich gibt die Methode wieder einen Promise zurück.
Hm, das heißt, das Parsen der JSON-Ressourcen kann nicht parallel erfolgen?
Parallel geht sowieso nichts im Single-Thread-Javascript.
Der Browser parst JSON auch nicht im JavaScript-Thread. Das kann in einem anderen Thread erfolgen. Währenddessen kann anderes JavaScript ausgeführt werden, anderes Parsing erfolgen usw. Natürlich muss Code, der auf den Promise wartet, auf das Parsingergebnis warten.
Daher halte ich Gunnar's Lösung für angemessen.
Du kannst jedoch die zwei fetch() mit then() in ein requests.map() stecken, dann sparst du dir den doppelten Code und hast den zeitlichen Vorteil.
Das stimmt.
Ludger
Tach!
response.json() ist kein asynchroner Prozess.
Doch, schließlich gibt die Methode wieder einen Promise zurück.
Ok, da lag ich falsch, JSON.parse() wäre synchron. Aber so richtig asynchron wird Response.json() nicht sein.
Hm, das heißt, das Parsen der JSON-Ressourcen kann nicht parallel erfolgen?
Parallel geht sowieso nichts im Single-Thread-Javascript.
Der Browser parst JSON auch nicht im JavaScript-Thread. Das kann in einem anderen Thread erfolgen. Währenddessen kann anderes JavaScript ausgeführt werden, anderes Parsing erfolgen usw. Natürlich muss Code, der auf den Promise wartet, auf das Parsingergebnis warten.
Das glaube ich jedoch nicht, dass extra für das Parsen ein Thread aufgemacht wird. Das wäre bei kleinen Mengen zu parsendem Code viel zu aufwendig. Und dann müsste dieses Handling eine andere Implementation haben als JSON.parse().
Daher halte ich Gunnar's Lösung für angemessen.
Du kannst jedoch die zwei fetch() mit then() in ein requests.map() stecken, dann sparst du dir den doppelten Code und hast den zeitlichen Vorteil.
Das stimmt.
An der Stelle meinte ich das übrigens so: Das Parsen findet vermutlicherweise direkt im Anschluss an den Callback statt und falls in der Zeit der zweite Request fertig wird, muss dessen Callback sich in der Event-Loop anstellen. Den zeitlichen Vorteil hat man nur zu dem Teil, der vor dem Eintreffen der Response fertig ist.
Das ist meine Mutmaßung, und wahrscheinlich wird das sowieso nicht relevant werden, weil alles wegen zu geringer Datenmengen im Grundrauschen untergeht. Deshalb halte ich auch Aufwand für vertane Zeit, das genauer zu untersuchen zu wollen.
dedlfix.
Hello,
ich finde, dass wir das doch noch weiter verfolgen sollten. Wenn das tatsächlich alles ohne Zutun des Programmierers stattfindet, sollte manzumindest wissen, wo man Semaphore o. ä. vorsehen muss und wie man die atomrisiert anlegen kann.
Witzigerweise nennt sich das dann Self-Hosted
Liebe Grüße
Tom S.
Wenn das tatsächlich alles ohne Zutun des Programmierers stattfindet, sollte manzumindest wissen, wo man Semaphore o. ä. vorsehen muss und wie man die atomrisiert anlegen kann.
Das Rechenmodell von Parallel.js ist darauf ausgelegt, dass du mit unveränderlichen Zuständen und ohne Nebenwirkungen programmierst. Das heißt, du vermeidest Race-Conditions grundsätzlich und begibst dich erst gar nicht in die missliche Lage ein Problem wechselseitigen Ausschlusses lösen zu müssen. Das spart viel Code, der mit dem eigentlichen Programmierproblem nichts zu tun gehabt hätte, der also nur der Buchhaltung gedient hätte. Man vermeidet außerdem Folgeprobleme, wie Deadlocks oder Fairness-Bedingungen. Der beste Code ist der, den man nie schreiben muss.
Witzigerweise nennt sich das dann Self-Hosted
Self-hosted benutzt man ganz allgemein für Programmiersprachen-Interpreter, (JIT-)Compiler und Ausführungsumgebungen, die teilweise in der Programmiersprache selbst geschrieben sind. Der C++-Compiler wird zum Beispiel auch in C++ geschrieben, eine neue Compiler-Version wird also von einer älteren Compiler-Version übersetzt.
Hello,
Witzigerweise nennt sich das dann Self-Hosted
Self-hosted benutzt man ganz allgemein für Programmiersprachen-Interpreter, (JIT-)Compiler und Ausführungsumgebungen, die teilweise in der Programmiersprache selbst geschrieben sind. Der C++-Compiler wird zum Beispiel auch in C++ geschrieben, eine neue Compiler-Version wird also von einer älteren Compiler-Version übersetzt.
Das war mir schon irgendwie klar. Ich fand nur den Namen so vertraut :-)
Gewundert habe ich mich allerdings, dass das Verfahren bei JavaScript auch sinnvoll sein soll. Ich hatte immer angenommen, dass das dann zu langsam ist, genauso, wenn man neue PHP-Funktionen in PHP entwickelt...
Oder ist hier gemeint, dass doch Object-Code erzeugt wird, der dann noch bereinigt wird, bevor er durch den Linker (oder was sonst anschließend kommt) geschickt wird?
Liebe Grüße
Tom S.
Ich hatte immer angenommen, dass das dann zu langsam ist, genauso, wenn man neue PHP-Funktionen in PHP entwickelt...
JavaScript ist um Größenordnungen schneller als PHP, das liegt unter anderem am Sprachdesign (zum Beispiel asynchronous IO) und daran, dass Microsoft, Google, Apple und Firefox in einem ständigen Wettbewerb um die schnellste JavaScript-Engine ringen.
Das Interesse an PHP unter Wirtschaftsriesen ist mit der Ausnahme von Facebook dagegen verschwindend gering, und selbst Facebook investiert lieber in den hauseigenen Dialekt „Hack“. Wieviel von dem Know-How zurück in PHP fließt, kann ich nicht einschätzen, aber offenbar nicht zu viel.
Oder ist hier gemeint, dass doch Object-Code erzeugt wird, der dann noch bereinigt wird, bevor er durch den Linker (oder was sonst anschließend kommt) geschickt wird?
Die meisten heutigen JS-Engines sind Just-In-Time-Compiler, also richtig da wird Object-Code erzeugt.
Doch, schließlich gibt die Methode wieder einen Promise zurück.
Ok, da lag ich falsch, JSON.parse() wäre synchron. Aber so richtig asynchron wird Response.json() nicht sein.
Die Erklärung stimmt auch nur halb. Promises können synchron oder asynchron resolved werden, zum Beispiel kann man auch das synchrone JSON.parse() in eine Promise-API wrappen:
function json (jsonString) {
return new Promise((resolve, reject) => {
try {
debugger;
const jsonObject = JSON.parse(jsonString);
resolve(jsonObject);
} catch(e) {
reject(e);
}
})
}
Ein Aufruf json('42')
würde den String '42' synchron/blockierend parsen und sofort ein Promise zurück geben, das den Status resolved
hat. An dem Breakpoint liegt der json
-Aufruf also auf dem Callstack. Erst wenn man das Ergebnis mit then
weiter vearbeitet, wird ein neuer Task in die Event-Queue gepusht.
Es gibt also bei Promise-APIs zwei Stellen an denen Asynchronität eine Rolle spielt:
pending -> resolved
(oder pending -> rejected
) kann asynchron oder synchron sein.then
ist immer asynchron.Das kann man sich vermutlich am einfachsten vor Augen führen, indem man sich mit den DevTools die Callstacks zu kritischen Zeitpunkten ansieht. Hab' da mal was gebastelt: https://jsfiddle.net/6mxbz9Lh/2/.
@@dedlfix
Du kannst jedoch die zwei fetch() mit then() in ein requests.map() stecken, dann sparst du dir den doppelten Code und hast den zeitlichen Vorteil.
Wo finde ich was dazu?
LLAP 🖖
Tach!
Du kannst jedoch die zwei fetch() mit then() in ein requests.map() stecken, dann sparst du dir den doppelten Code und hast den zeitlichen Vorteil.
Wo finde ich was dazu?
Mehrere Vorschläge dazu fand ich bei stackoverflow, gesucht nach javascript fetch promise.all.
Konkret meinte ich diesen Vorschlag von dort:
Promise.all(urls.map(url =>
fetch(url).then(resp => resp.text())
)).then(texts => {
…
})
Der macht das mit URLs statt Requests, aber das ist ja das gleiche Prinzip.
dedlfix.
@@dedlfix
Du kannst jedoch die zwei fetch() mit then() in ein requests.map() stecken, dann sparst du dir den doppelten Code und hast den zeitlichen Vorteil.
Wo finde ich was dazu?
Mehrere Vorschläge dazu fand ich bei stackoverflow, gesucht nach javascript fetch promise.all.
Konkret meinte ich diesen Vorschlag von dort:
Promise.all(urls.map(url => fetch(url).then(resp => resp.text()) )).then(texts => { … })
Der macht das mit URLs statt Requests, aber das ist ja das gleiche Prinzip.
Meintest du das so?
var aRequest = new Request('a.json');
var bRequest = new Request('b.json');
Promise.all(
[aRequest, bRequest].map(
request => fetch(request).then(response => response.json())
)
).then(values => {
var aData = values[0];
var bData = values[1];
// do something with aData and bData
});
Ja, das läuft. Irgendwie kann ich mich mit den Pfeilen =>
noch nicht anfreunden.
LLAP 🖖
@@Gunnar Bittersmann
Der macht das mit URLs statt Requests, aber das ist ja das gleiche Prinzip.
Was ist überhaupt der Unterschied zwieschen fetch(url)
und fetch(new Request(url))
?
Mit Array der benötigten Ressourcen sieht’s so aus:
var urls = [
'a.json',
'b.json'
];
Promise.all(
urls.map(
url => fetch(url).then(response => response.json())
)
).then(values => {
var aData = values[0];
var bData = values[1];
// do something with aData and bData
});
Unschön daran ist noch, dass man später beim Auslesen von values
sich genau an die Reihenfolge halten muss wie man sie im Array urls
festgelegt hatte – ohne dass dies im Code ersichtlich wäre.
Wie kommt man aus der Nummer raus? urls
als Array von Objekten?
var urls = [
{
name: 'aData',
url: 'a.json'
},
{
name: 'bData',
url: 'b.json'
}
];
LLAP 🖖
Tach!
Was ist überhaupt der Unterschied zwieschen
fetch(url)
undfetch(new Request(url))
?
Sieht aus wie: nichts. Vermutlich kann man mit dem Request-Objekt noch eine Menge mehr Dinge anstellen. Andererseits kann man dem fetch() (genau wie dem Request-Konstruktor) auch einen zweiten Parameter mit Optionen übergeben.
Unschön daran ist noch, dass man später beim Auslesen von
values
sich genau an die Reihenfolge halten muss wie man sie im Arrayurls
festgelegt hatte – ohne dass dies im Code ersichtlich wäre.
Zumindest kann man mit Array-Dekonstruktion den Zugriff über Nummern einsparen
// var aData = values[0];
// var bData = values[1];
var [aData, bData] = values;
Wie kommt man aus der Nummer raus?
Wohl eher nicht, weil das Promise.all() nur mit Iterables arbeitet und da nur eine reihenfolgenbasierte Zugriffgeschichte geht.
dedlfix.
Lieber Gunnar,
fetch(aRequest) .then(function (response) { return response.json(); }) .then(function (aData) { // … }); [...]
Aber was müsste dann anstelle von
// …
stehen, damitdoSomething(aData, bData)
aufgerufen wird, sobald beide JSON-Dateien geladen sind?
so wie ich das Deinem Code entnehme, müsste man jeweils prüfen, ob der andere Code auch schon da ist, um erst dann diese Code-Stelle (// …
) auszuführen. Deiner Logik nach werden ja beide JSON-Daten unabhängig voneinander geladen, sodass eine ganz sicher zuletzt geladen wird, welche dann die eigentliche Abarbeitung anstoßen müsste. Und sollte ein Ladevorgang scheitern, dann ist das Ausführen ohnehin hinfällig.
Vielleicht etwas in dieser Art?
fetch(aRequest)
.then(function (response) { return response.json(); })
.then(function (aData)
{
this.jsonData = aData;
// check if other request has already loaded
if (bRequest.jsonData) {
processRequests();
}
});
fetch(bRequest)
.then(function (response) { return response.json(); })
.then(function (bData)
{
this.jsonData = bData;
// check if other request has already loaded
if (aRequest.jsonData) {
processRequests();
}
});
function processData () {
// aRequest.jsonData && bRequest.jsonData
}
Oder meintest Du etwas völlig anderes?
Liebe Grüße,
Felix Riesterer.
Das Problem ist bereits hier: function doSomething(aData, bData)
weil du nicht einfach auf zwei Variablen greifen kannst wo du doch noch gar nicht weißt ob die Response angekommen ist. Ich würde mir beide Dateien in einer Response schicken. MfG
Tach!
Das Problem ist bereits hier:
function doSomething(aData, bData)
weil du nicht einfach auf zwei Variablen greifen kannst wo du doch noch gar nicht weißt ob die Response angekommen ist.
Das ist kein Problem, das ist die zu lösende Aufgabe. Der Aufruf soll ja erst erfolgen, nachdem die Daten in den beiden Variablen vorliegen, also wenn die Promises der beiden fetch()-Aufrufe erfüllt sind. Da greift niemand vorzeitig auf Variablen, ohne dass die Response da ist.
Ich würde mir beide Dateien in einer Response schicken.
Ohne zu wissen, warum das zwei Aufrufe und nicht bereits von vorn herein nur einer ist, würde ich nicht pauschal zu einem Zusammenlegen raten. Zudem gibt es mit Promise.all() eine einfache Lösung für das Problem, ohne dass serverseitig etwas umgebaut werden muss.
dedlfix.
@@dedlfix
Ich würde mir beide Dateien in einer Response schicken.
Ohne zu wissen, warum das zwei Aufrufe und nicht bereits von vorn herein nur einer ist, würde ich nicht pauschal zu einem Zusammenlegen raten.
Das eine ist Bibliothek, in dem Fall Sprachennamen:
{
"art-x-navi": "Na’vi",
"qya": "Quenya",
"tlh": "tlhIngan Hol"
}
Das andere ist Konfiguration, in dem Fall die bei der Anwendung verfügbaren Sprachen:
["art-x-navi", "tlh"]
(Sorry, Mittelerde, nicht deine Zeit.)
Zudem gibt es mit Promise.all() eine einfache Lösung für das Problem, ohne dass serverseitig etwas umgebaut werden muss.
Zumal die Ressourcen ja gar nicht vom selben Server kommen müssen. Das können verschiedene eigene Webservices sein; die Bibliothek könnte aber auch von einem Fremdanbieter kommen.
Q. Und warum überhaupt JavaScript fürs Sprachmenü?
A. Weil sich’s um eine Demo (kein Prototyp) handelt.
Q. Und warum nicht die Lösung mit der Verschachtelung? Sollte doch für eine Demo gut genug sein?
A. Weil ich wissen wollte, wie man’s richtig macht. Für später.
LLAP 🖖
@@Gunnar Bittersmann
Sieht jetzt so aus:
lib/language-names.json:
{
"de": "deutsch",
"en": "English",
"es": "español",
"tlh": "tlhIngan Hol"
}
lib/language-names.de.json:
{
"de": "deutsch",
"en": "englisch",
"es": "spanisch",
"tlh": "klingonisch"
}
config/available-languages:
["de", "es", "en"]
Markup (HTML):
<ul id="lang-menu">
<template id="lang-menu-item">
<li><a></a></li>
</template>
<script src="lang-menu.js" async=""></script>
</ul>
lang-menu.js:
var currentLanguage = document.documentElement.lang;
var urls = [
'config/available-languages.json',
'lib/language-names.json',
'lib/language-names.' + currentLanguage + '.json'
];
Promise.all(
urls.map(
url => fetch(url).then(response => response.json())
)
).then(values => {
var [availableLanguages, languageNames, localLanguageNames] = values;
var template = document.querySelector('#lang-menu-item');
availableLanguages.forEach(lang => {
if (lang != currentLanguage)
{
var clone = template.content.cloneNode(true);
var liElement = clone.querySelector('li');
var aElement = liElement.querySelector('a');
aElement.href = '?lang=' + lang;
aElement.hreflang = lang;
aElement.lang = lang;
aElement.textContent = languageNames[lang] || lang;
liElement.title = localLanguageNames[lang] || '';
template.parentNode.appendChild(clone);
}
});
});
Daraus generiertes Markup (DOM), wenn wir uns auf der deutschsprachigen Seite befinden (<html lang="de">
:
<ul id="lang-menu">
<template id="lang-menu-item"></template>
<script src="lang-menu.js" async=""></script>
<li title="englisch"><a href="?lang=en" hreflang="en" lang="en">English</a></li>
<li title="spanisch"><a href="?lang=es" hreflang="es" lang="es">español</a></li>
</ul>
LLAP 🖖
@@Gunnar Bittersmann
Ich mach mal den Columbo: Eine Frage hätte ich dann doch noch.
Im Script kann man jedes var
durch let
ersetzen. Aber was wäre wirklich stilvoll? Wo wäre var
angebracht, wo let
?
var currentLanguage = document.documentElement.lang; var urls = [ 'config/available-languages.json', 'lib/language-names.json', 'lib/language-names.' + currentLanguage + '.json' ]; Promise.all( urls.map( url => fetch(url).then(response => response.json()) ) ).then(values => { var [availableLanguages, languageNames, localLanguageNames] = values; var template = document.querySelector('#lang-menu-item'); availableLanguages.forEach(lang => { if (lang != currentLanguage) { var clone = template.content.cloneNode(true); var liElement = clone.querySelector('li'); var aElement = liElement.querySelector('a'); aElement.href = '?lang=' + lang; aElement.hreflang = lang; aElement.lang = lang; aElement.textContent = languageNames[lang] || lang; liElement.title = localLanguageNames[lang] || ''; template.parentNode.appendChild(clone); } }); });
LLAP 🖖
Hello,
Ich mach mal den Columbo: Eine Frage hätte ich dann doch noch.
Im Script kann man jedes
var
durchlet
ersetzen. Aber was wäre wirklich stilvoll? Wo wärevar
angebracht, wolet
?
Dumm zurückgefragt:
Beschreiben die beiden nicht unterschiedliche Scopes?
Liebe Grüße
Tom S.
Hallo TS,
Beschreiben die beiden nicht unterschiedliche Scopes?
Ja.
Bis demnächst
Matthias
Hallo Gunnar Bittersmann,
Im Script kann man jedes
var
durchlet
ersetzen. Aber was wäre wirklich stilvoll? Wo wärevar
angebracht, wolet
?
Vorsicht rot. Tendenz steigend?
Bis demnächst
Matthias
@@Matthias Apsel
?? Grün, soweit das Auge reicht.
Und Browser, die let
nicht verstehen, sind hier sowieso aus dem Rennen, da sie auch fetch()
nicht verstehen.
LLAP 🖖
Hallo Gunnar Bittersmann,
?? Grün, soweit das Auge reicht.
Man muss "show all" wählen, dann bekommt man den auch UC-Browser angezeigt, der in Europa zugegebenermaßen keine Rolle spielt.
Bis demnächst
Matthias
Das sollte niemanden mehr aufhalten, es gibt Pre-Compiler die sorgen schon dafür, dass moderner JavaScript-Code auch in älteren Browsergenerationen funktioniert. Babel und TypeScript, nur um mal die zwei Größten zu nennen.
Hallo Gunnar
Im Script kann man jedes
var
durchlet
ersetzen. Aber was wäre wirklich stilvoll? Wo wärevar
angebracht, wolet
?
Vielleicht hilft dir die Lektüre dieses und dieses Beitrags von mir bei der Entscheidungsfindung.
Edit: Und dieser Beitrag im selben Thread befasst sich ebenfalls mit dem Thema.
Gruß,
Orlok
@@Orlok
Vielleicht hilft dir die Lektüre dieses und dieses Beitrags von mir bei der Entscheidungsfindung.
Weil „Variablen prinzipiell so lokal wie möglich angelegt werden sollten“ also nirgends var
. Und da sämtliche Variablen ihren einmal zugewiesenen Wert behalten, überall const
. So also?
const currentLanguage = document.documentElement.lang;
const urls = [
'config/available-languages.json',
'lib/language-names.json',
'lib/language-names.' + currentLanguage + '.json'
];
Promise.all(
urls.map(
url => fetch(url).then(response => response.json())
)
).then(values => {
const [availableLanguages, languageNames, localLanguageNames] = values;
const template = document.querySelector('#lang-menu-item');
availableLanguages.forEach(lang => {
if (lang != currentLanguage)
{
const clone = template.content.cloneNode(true);
const liElement = clone.querySelector('li');
const aElement = liElement.querySelector('a');
aElement.href = '?lang=' + lang;
aElement.hreflang = lang;
aElement.lang = lang;
aElement.textContent = languageNames[lang] || lang;
liElement.title = localLanguageNames[lang] || '';
template.parentNode.appendChild(clone);
}
});
});
LLAP 🖖
Hallo Gunnar
Vielleicht hilft dir die Lektüre dieses und dieses Beitrags von mir bei der Entscheidungsfindung.
Weil „Variablen prinzipiell so lokal wie möglich angelegt werden sollten“ also nirgends
var
. Und da sämtliche Variablen ihren einmal zugewiesenen Wert behalten, überallconst
. So also?
Im Prinzip ja.
.then(values => { const [availableLanguages, languageNames, localLanguageNames] = values;
Diese Zeile ist aber eigentlich überflüssig.
.then(([availableLanguages, languageNames, localLanguageNames]) => {
Du kannst das Array mit dem der Parameter values
initialisiert wird auch direkt in der Parameterliste destrukturieren. Dabei müssen allerdings wie in dem Beispiel oben Klammern gesetzt werden, sonst gibt das einen Syntaxfehler.
const clone = template.content.cloneNode(true); const liElement = clone.querySelector('li'); const aElement = liElement.querySelector('a');
Hier könnte man darüber nachdenken, ob es wirklich alle drei const
braucht.
aElement.href = '?lang=' + lang; aElement.hreflang = lang; aElement.lang = lang; aElement.textContent = languageNames[lang] || lang;
Wenn mehreren Eigenschaften eines Objektes Werte zugewiesen werden sollen, dann mache ich das ganz gern mit Object.assign
. Da ein Zuweisungsausdruck grundsätzlich zu dem zugewiesenen Wert aufgelöst wird, lässt es sich in solchen Fällen leider nicht vermeiden, das Objekt, welches das Ziel der Zuweisung ist, jedes mal aufs neue zu referenzieren. Das kann man sich mit assign
sparen.
Object.assign(liElement.querySelector('a'), {
href: '?lang=' + lang,
hreflang: lang,
lang: lang,
textContent: languageNames[lang] || lang
});
Zurückgegeben wird von assign
das Zielobjekt, welches als ersts Argument übergeben wird, aber da die Referenz hier nicht benötigt wird, kann man auf die Deklaration einer Konstante für das Element verzichten. Ich finde, dass schaut ein wenig eleganter aus. Aber das ist nur eine Idee. ;-)
{
lang: lang
}
Das könnte man dann wenn man wollte auch noch abkürzen und einfach lang,
schreiben. Dann wird der Parameter referenziert, sein Bezeichner als Eigenschaftsname verwendet und sein Wert zugewiesen.
Edit:
Ich habe gerade beim Überfliegen des Threads gesehen, dass dein Markup so aussieht:
<ul id="lang-menu"> <template id="lang-menu-item"> <li><a></a></li> </template> <script src="lang-menu.js" async=""></script> </ul>
Das sieht nicht so aus, als würde sich da regelmäßig was ändern, also würde ich an dieser Stelle vermutlich auf die Methode querySelector()
verzichten und die beiden Elemente statt dessen über die Eigenschaft firstElementChild
referenzieren.
Viele Grüße,
Orlok
@@Orlok
Vielen Dank für deine Erläuterungen. Einiges davon würde ich aber wegen „kürzerer Code ist nicht unbedingt besser lesbar“ verwerfen. Aber gut zu wissen, was alles möglich ist.
Wenn mehreren Eigenschaften eines Objektes Werte zugewiesen werden sollen, dann mache ich das ganz gern mit
Object.assign
.
Object.assign
ist das neue with
? 😉
<template id="lang-menu-item">
Das sieht nicht so aus, als würde sich da regelmäßig was ändern
Da bin ich mir nicht so sicher. Ich bin mir nicht sicher, ob li
das geeignete Element für das title
-Attribut ist. Vielleicht gehört das ja ans a
-Element; dann bräuchte dieses noch ein inneres span
:
<li>
<a href="?lang=es" hreflang="es" title="spanisch">
<span lang="es">español</span>
</a>
</li>
also würde ich an dieser Stelle vermutlich auf die Methode
querySelector()
verzichten und die beiden Elemente statt dessen über die EigenschaftfirstElementChild
referenzieren.
Ich würde das JavaScript so robust halten, dass es auch bei kleineren Markup-Änderungen noch funktioniert.
LLAP 🖖
Im Script kann man jedes
var
durchlet
ersetzen. Aber was wäre wirklich stilvoll? Wo wärevar
angebracht, wolet
?
Du kannst sogar alle Vorkommen von var
durch const
ersetzen und das würde ich auch tun. Es signalisiert den Leser(innen), dass die Variablen während ihrer gesamten Lebensdauer nicht mehr überschrieben werden und erspart ihnen dadurch viel Gehirnschmalz.
Hallo Gunnar,
kannst Du das bitte genauer spezifizieren?
`Ist aber nicht richtig asynchron. Die Dateien sollten unabhängig voneinander parallel geladen werden:´
Sind das zwei eigensständige Threads oder ist die Abarbeitung serialisiert?
Grüße Tote Locke
Hallo Gunnar,
Aber was müsste dann anstelle von
// …
stehen, damitdoSomething(aData, bData)
aufgerufen wird, sobald beide JSON-Dateien geladen sind?
in einem ähnlichen Fall mache ich es so, dass jeder Request ein Flag setzt und dann prüft, ob die anderen auch schon fertig sind.
In einem anderen Fall setzt auch jeder Request ein Flag. Die Auswertefunktion fragt dann über ein sinnvoll getaktetes setInterval
die Flags ab.
Gruß
Jürgen
Hallo JürgenB,
da fehlt JS einfach die Möglichkeit, einen Eventlistener für Variablen anzulegen. Dann wäre die Aufgabe elegant lösbar.
Nur Mut, Ex & wait...
Tach!
da fehlt JS einfach die Möglichkeit, einen Eventlistener für Variablen anzulegen. Dann wäre die Aufgabe elegant lösbar.
Ist mittlerweile eingebaut, nennt sich Promise.
dedlfix.
Hallo,
Ist mittlerweile eingebaut, nennt sich Promise.
leider bleibt der IE da draußen vor, bzw. braucht etwas Nachhilfe.
Gruß
Jürgen
Hallo JürgenB,
bei fetch auch. Polyfills to the rescue :)
Rolf