Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.

Link zu der Vergleichsansicht

Beide Seiten, vorherige Überarbeitung Vorherige Überarbeitung
Nächste Überarbeitung
Vorherige Überarbeitung
ef_informatik:web_app [2021-08-26 06:39] – [Javascript 2: Asynchronous code] hofef_informatik:web_app [2021-12-15 13:41] (aktuell) – [Async Functions] hof
Zeile 72: Zeile 72:
 # Javascript # Javascript
  
-Die meisten Browser können Javascript (oder ECMAScript) ausführen, um Webseiten dynamisch zu gestalten. Die Syntax ist ähnlich wie C++, C# oder Java, am bestem am Beispiel:+Die meisten Browser können Javascript (oder ECMAScript) ausführen, um Webseiten dynamisch zu gestalten. Die Syntax ist ähnlich wie `C++`, C# oder Java, am bestem am Beispiel:
  
 <file ecmascript connect_four.js> <file ecmascript connect_four.js>
Zeile 178: Zeile 178:
   * Hinweis: [[https://developer.mozilla.org/de/docs/Learn/JavaScript/Objects/JSON|JSON Referenz]]   * Hinweis: [[https://developer.mozilla.org/de/docs/Learn/JavaScript/Objects/JSON|JSON Referenz]]
  
 +## Musterlösung 2
 +
 +Wie immer gilt: es gibt unendlich viele korrekte Lösungen. {{ :ef_informatik:ksr_ef_viergewinnt-js_game.zip |}}
  
 # HTTP # HTTP
Zeile 185: Zeile 188:
 ## Request & Response ## Request & Response
 {{ :ef_informatik:http_404.jpg?nolink&500|}} {{ :ef_informatik:http_404.jpg?nolink&500|}}
-Jeder Request verlangt eine bestimmte //Ressource// mit einer bestimmten //Method//. Die //Method// beschreibt die Aktion, typischerweise //GET//, um eine Ressource zu holen, //PUT// um eine Ressource auf dem Server zu verändern.+Jeder Request verlangt eine bestimmte //Ressource// mit einer bestimmten //Method//. Die //Method// beschreibt die Aktion, typischerweise //GET//, um eine Ressource zu holen, //POST// um eine Ressource auf dem Server zu verändern.
  
 Der Request enthält eine Anzahl zusätzlicher Informationen als //Headers//, insbesondere: Der Request enthält eine Anzahl zusätzlicher Informationen als //Headers//, insbesondere:
Zeile 207: Zeile 210:
  
 //Session-Cookies// leben nur, solange das Browser-Fenster offen ist und sind in der Regel unproblematisch. Long-lived cookies können über Jahre installiert bleiben und bei jedem Seitenbesuch erneuert werden. Sie werden zum Teil benützt, um Benutzer über Webseiten hinweg zu verfolgen, und haben zur ganzen Diskussion um Cookies geführt. Um den Benutzer zu identifizieren (zum Beispiel nach erfolgreichem Login) sind sie aber unverzichtbar. //Session-Cookies// leben nur, solange das Browser-Fenster offen ist und sind in der Regel unproblematisch. Long-lived cookies können über Jahre installiert bleiben und bei jedem Seitenbesuch erneuert werden. Sie werden zum Teil benützt, um Benutzer über Webseiten hinweg zu verfolgen, und haben zur ganzen Diskussion um Cookies geführt. Um den Benutzer zu identifizieren (zum Beispiel nach erfolgreichem Login) sind sie aber unverzichtbar.
- 
-## Javascript Fetch 
-Requests werden im klassischen Web für eine HTML-Ressource und die darin eingebundenen Ressourcen (Stylesheets, Bilder, Javascript) ausgelöst. Sie können aber auch vom Javascript-Code angefordert werden. Dieser Mechanismus ([[https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch|Fetch]]) wird von allen modernen Webapps benutzt, um Inhalte dynamisch nachzuladen. Beispielsweise lädt Google Maps Kartenkacheln nach, wenn der Kartenausschnitt durch Zoomen oder Verschieben verändert wird. 
- 
-Wir werden diese Technik verwenden, um den Spielstand auf dem Server nachzuladen, wenn der andere Spieler gespielt hat. 
  
 # Web-App mit Flask # Web-App mit Flask
Zeile 223: Zeile 221:
 Um diese Problem zu lösen, werden wir die App auf einen Web-Server verschieben.  Um diese Problem zu lösen, werden wir die App auf einen Web-Server verschieben. 
   * Install Flask mit ''pip3 install flask''.   * Install Flask mit ''pip3 install flask''.
-  * Move static files to ''/static''.+  * Use the [[https://flask.palletsprojects.com/en/2.0.x/quickstart/|quick start guide]] to help you with the following tasks. 
 +  * Transfer existing code to web app: 
 +    * Move static files to ''/static''. 
 +    * Create ''app.py'' in root folder to create the simple-most web app. 
 +<code python app.py> 
 +from flask import Flask 
 + 
 +app = Flask(__name__) 
 + 
 +@app.route("/"
 +def hello_world(): 
 +    return "<p>Hello, World!</p>" 
 +</code>
   * Turn HTML into a Jinja2 template.   * Turn HTML into a Jinja2 template.
-  * Add JSON endpoints+  * Create a connect-four game model in python. 
 +  * Our App will communicate with the Server through JSON endpoints 
 +    * see an [[https://de.wikipedia.org/wiki/Representational_State_Transfer#Beispiel|example of a REST endpoint]] 
 +    * we need endpoints for 
 +      * fetching gamestate 
 +      * inserting a piece 
 +      * tricky part: identify the user using [[https://flask.palletsprojects.com/en/2.0.x/quickstart/#sessions|sessions]] 
 +  * Instead of holding game state on the client side, fetch it from the server.
  
-# Javascript 2: Asynchronous code+Weitere Ideen: 
 +  * Long polling für den Game-State 
 +  * Websockets (nicht direkt unterstützt in Flask, aber in [[https://flask-socketio.readthedocs.io/|Flask-SocketIO]] oder [[https://pgjones.gitlab.io/quart/|Quart]]) 
 + 
 +# Javascript 2 
 + 
 +## Fetch 
 +Requests werden im klassischen Web für eine HTML-Ressource und die darin eingebundenen Ressourcen (Stylesheets, Bilder, Javascript) ausgelöst. Sie können aber auch vom Javascript-Code angefordert werden. Dieser Mechanismus ([[https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch|Fetch]]) wird von allen modernen Webapps benutzt, um Inhalte dynamisch nachzuladen. Beispielsweise lädt Google Maps Kartenkacheln nach, wenn der Kartenausschnitt durch Zoomen oder Verschieben verändert wird. 
 + 
 +Wir werden diese Technik verwenden, um den Spielstand auf dem Server nachzuladen, wenn der andere Spieler gespielt hat. 
 + 
 +## Asynchronous code
  
 Eine Herausforderung in Javascript (und nicht nur dort) ist der Umgang mit längeren Aufgaben. Reagiert unser Code ja auf einen Event (z.B. einen Klick) und führt dann eine kurze Aufgabe (z.B. den Game-State ändern) aus, ist das kein Problem. Wollen wir eine potentiell längere Aufgabe (insbesondere: Network-Traffic über ''fetch()'', oder eine Animation) ausführen, reklamiert der Browser, weil wir damit den Event-Thread blockieren. Das heisst, dass andere Events nicht richtig ausgeliefert werden können, und die App "eingefroren" ist.  Eine Herausforderung in Javascript (und nicht nur dort) ist der Umgang mit längeren Aufgaben. Reagiert unser Code ja auf einen Event (z.B. einen Klick) und führt dann eine kurze Aufgabe (z.B. den Game-State ändern) aus, ist das kein Problem. Wollen wir eine potentiell längere Aufgabe (insbesondere: Network-Traffic über ''fetch()'', oder eine Animation) ausführen, reklamiert der Browser, weil wir damit den Event-Thread blockieren. Das heisst, dass andere Events nicht richtig ausgeliefert werden können, und die App "eingefroren" ist. 
  
 Wir wollen längere Aufgaben //asynchron// oder //nebenläufig// ausführen können. Modernes Javascript unterstützt die Programmierung von asynchronem Code mit zwei Konstrukten: ''Promises'' und den ''async'' / ''await'' Keywords, die auf ''Promise'' aufbauen. Wir wollen längere Aufgaben //asynchron// oder //nebenläufig// ausführen können. Modernes Javascript unterstützt die Programmierung von asynchronem Code mit zwei Konstrukten: ''Promises'' und den ''async'' / ''await'' Keywords, die auf ''Promise'' aufbauen.
 +
 +### Promise
 +
 +Eine ''Promise'' ist ein //Versprechen// für das Resultat einer nebenläufigen Aufgabe. Die Promise ist zu beginn im Zustand ''pending'' und geht von da entweder über zu ''fulfilled'' (die Aufgabe war erfolgreich) oder ''rejected'' (die Aufgabe ist fehlgeschlagen). ''fulfilled'' und ''rejected'' werden zusammen als ''settled'' bezeichnet.
 +
 +Mit Promises kann ich eine Sequenz von voneinander abhängigen Aufgaben zusammenstellen, ohne dass diese voneinander wissen müssen. Dafür nutzt man die Methoden ''then()'' und ''catch()'', die beide als Argument eine Funktion haben, die erst ausgeführt wird, wenn die Promise fullfilled (bei ''then'') oder rejected (bei ''catch'') wird:
 +
 +<code javascript>
 +
 +class ConnectFourView {
 +  updateUi(json) {
 +    // Updates the view using json.cells
 +  }
 +  
 +  fetchUpdateFromServer() {
 +    fetchPromise = fetch('/gamestate');
 +    jsonPromise = fetchPromise.then(response => response.json());
 +    updatePromise = jsonPromise.then(json => updateUi(json));
 +    updatePromise.catch(error => console.log(error));
 +  }
 +}
 +
 +</code>
 +
 +Was geht in ''fetchUpdateFromServer'' vor?
 +  * Fetch beginnt den gegebenen URL zu laden und gibt eine Promise zurück, die in Zukunft erfüllt werden wird.
 +    * Ist ''fetch'' erfolgreich, wird eine ''[[https://developer.mozilla.org/en-US/docs/Web/API/Response|Response]]'' zurückgegeben.
 +  * Mit ''then'' hängen wir der Promise eine weitere Funktion an, die erst ausgeführt wird, wenn die erste erfolgreich war.
 +    * Die Funktion erhält als Argument das Resultat der vorangehenden Promise, also die ''Response''
 +    * ''Response.json()'' gibt eine Promise zurück auf den geparsten JSON-Inhalt der Response.
 +  * Ist die zweite Promise erfolgreich, passen wir die View anhand des JSON-Inhalts an.
 +    * Die letzte Promise erhält einen Failure-Handler (''catch''). Schlägt eine der vorgelagerten Promises fehl, wird auch die letzte Promise rejected, der Fehler wird in der Konsole ausgegeben.
 +
 +Merke:
 +  * Wenn die Funktion ''fetchUpdateFromServer'' fertig ausgeführt wird, ist das Update noch lange nicht erfolgt. Die Promise-Kette wird nebenläufig ausgeführt, die Seite "gefriert" nicht.
 +
 +Das Ganze lässt sich mit Chaining auch kompakter darstellen:
 +
 +<code javascript>
 +  fetchUpdateFromServer() {
 +    fetch('/gamestate')
 +      .then(response => response.json())
 +      .then(json => updateUi(json))
 +      .catch(error => console.log(error));
 +  }
 +</code>
 +
 +### Async Functions
 +
 +Promises sind grossartig, aber der Code ist etwas gewöhnungsbedürftig. Seit 2017 kann dasselbe eleganter geschrieben werden:
 +
 +<code javascript>
 +  async fetchUpdateFromServer() {
 +    try {
 +      const response = await fetch('/gamestate');
 +      const json = await response.json();
 +      updateUi(json));
 +    } catch (error) {
 +      console.log(error);
 +    }
 +  }
 +</code>
 +
 +Merke:
 +  * ''await'' wartet auf das Fulfilment einer Promise. Das bedeutet auch, dass die Methode lange dauern könnte.
 +  * ''await'' ist deshalb nur in ''async'' Funktionen erlaubt. Async Funktionen geben immer eine Promise zurück, auch wenn das im Code nicht mehr sichtbar ist.
 +  * Fehler können mit dem gewohnten ''try-catch'' gefangen werden.
  
   * read up on ''async'' and ''await'' [[https://developers.google.com/web/fundamentals/primers/async-functions|here]]   * read up on ''async'' and ''await'' [[https://developers.google.com/web/fundamentals/primers/async-functions|here]]
   * [[https://web.dev/promises/|more on Promises]]   * [[https://web.dev/promises/|more on Promises]]
  
 +{{ :ef_informatik:javascript_async_fetch_promises.png?nolink&600 | }}
  • ef_informatik/web_app.1629959984.txt.gz
  • Zuletzt geändert: 2021-08-26 06:39
  • von hof