1. Thema 1: Typescript
07.04.2022 Gruppe 52
1.1. Entstehung von Typescript
TypeScript wurde von Microsoft auf der Basis von JavaScript entwickelt, um besser Großanwendungen entwickeln zu können, während heute sich TypeScript vor allem zum Beispiel durch die spezifische Typisierung sowohl ihrem Just in Time Transpiler auszeichnet. Ein wichtiger Teil von TypeScript ist ebenfalls die Möglichkeit der asynchronen Programmierung sowohl als auch die Fähigkeit Event-Loops anzuwenden.
1.2. Motivation
Die Motivation des Teams TypeScript als Thema zu benutzen stammt zum einen von dem Faktor, dass TypeScript eine sehr weit verbreitete Sprache ist, die von vielen Firmen und von vielen Frameworks benutzt werden. Dazu kommt, dass ein Teil unserer Gruppe bereits Vorerfahrung mit der Sprache haben und alle die Sprache nochmal genauer unter die Lupe nehmen wollten.
1.3. Aufgabenstellung
Die Gruppe hat den Bericht untereinander aufgeteilt. Die Einleitung sowohl die Entstehung und Geschichte von TypeScript wurde von Niklas Kaspar verfasst. Der nächste Abschnitt erklärt die in TypeScript vorhandenen Features und die Nutzung unter Verwendung eines Transpiler, dieser Abschnitt wurde von Frank Bänecke verfasst. Einen tieferen Einblick in die TypeScript Funktionen wie asynchrone Programmierung und Event Loops gibt Sebastian Weh. Zum Schluss wird von Florian Kugelmann in verschiedenen Anwendungsbeispielen die Funktion und Anwendung von TypeScript vorgezeigt.
1.4. Entstehung und Geschichte von TypeScript
TypeScript wurde von Microsoft auf der bereits vorhandenen und erfolgreichen Sprache JavaScript basiert. Daher ist es möglich, jeden Javascript Code auch über TypeScript laufen zu lassen und JavaScript Bibliotheken wie jQuery und AngularJS in TypeScript zu benutzen. TypeScript wurde zur Entwicklung von großen Anwendungen konzipiert. Am 1. Oktober 2012 wurde TypeScript nach zwei Jahren Entwicklung zum erstem Mal veröffentlicht. Die erste öffentlich zugängliche TypeScript Version war die Version 0.8. Jedoch konnte bei der Veröffentlichung TypeScript nur in der Microsoft eigenen Entwicklungsumgebung Visual Studio Code benutzt werden. Dies war vor allem nicht günstig, da zu dieser Zeit Visual Studio nicht in Linux oder MacOS zur Verfügung stand. Im Jahre 2013 wurde, dann für die Entwicklungsumgebung Eclipse ein Plugin für die Benutzung von TypeScript veröffentlicht.
Weiterhin wurde am 19. Juni 2013 TypeScript 0.9 veröffentlicht, wo eine Unterstützung für Generischen Typen zu TypeScripts Repertoire hinzugefügt wurde. Die Version 1.0 wurde dann direkt im nächsten Jahr groß bei Microsofts eigener Entwicklerkonferenz Build vorgestellt und beworben. Ein weiterer Moment für TypeScript in 2014 war, als die Entwickler einen neuen Compiler ankündigten, der bis zu fünfmal schneller sein sollte als der alte Compiler. Die Version 2.0 wurde, dann am 22. September 2016 veröffentlicht und brachte verschiedene neue Funktionen mit sich, unter anderem die Möglichkeit zu verhindern, dass Variablen mit null initialisiert werden. TypeScript 3.0 wurde am 30. Juli 2018 veröffentlicht und hat Änderungen wie Tupel im Ruheparameter und weitere Sprachzusätze gebracht. Am 20. August 2020 wurde die Version 4.0 herausgebracht. Diese TypeScript Version hat vor allem neue Sprachfunktionen wie benutzerdefinierte JSX-Fabriken hinzugefügt. Der momentane Stand von TypeScript zum Zeitpunkt dieses Berichts ist die Version 4.6.2, die am 1. März 2022 herausgekommen ist.
1.5. Features und Nutzung
1.5.1. Features
TypeScript lässt sich als Obermenge („strict superset“) zu ‚ECMAScript 2015‘ definieren, was wiederum eine Obermenge zu ‚ECMAScript 5‘, der Vorgängerversion, ist. So bietet es viele Features über das Repertoire von JavaScript hinaus, wobei manche in ‚ECMAScript 2015‘ ebenfalls übernommene Konstrukte sind, die für ältere JavaScript-Versionen zur Verfügung gestellt werden („Backport“).
Erweiterungen zu ‚ECMAScript 2015‘:
Methodensignatur Prüfung zu Kompilierzeit
Typinferenz
Type Erasure
Interfaces
Aufzählungstypen
Generische Programmierung
Namensräume
Tupel
Async/Await
Backport von ‚ECMAScript 2015‘:
Klassen
Module
Arrow-Syntax für anonyme Funktionen
Optionale Parameter und Standardparameter
Typisierung
Der Namensgebende Hauptpunkt ist die Typisierung, welche beispielsweise durch die Methodensignaturen ermöglicht wird. Diese wird beim transpilieren in JavaScript geprüft, wodurch Typsicherheit gewährleistet werden kann.
1.5.2. Nutzung und Kompatibilität
Typescript wird, der Nutzung von JavaScript folgend, vor allem in Browsern für Clientseitige Funktionalitäten von Webseiten genutzt. Jedoch findet es auch sonst immer mehr Anwendung, wenn JavaScript eine Rolle spielt, wie beispielsweiße bei der plattformunabhängigen Laufzeitumgebung Node.js, welche JavaScript-Code unter Anderem auch serverseitig einsetzbar macht.
Transpiler
Während korrekter JavaScript-Code, aufgrund der Obermengen-Beziehung, auch immer valider TypeScript-Code ist, müssen Programme umgekehrt zunächst in JavaScript übersetzt werden. Dies wird vom Typescript-Transpiler, oder verschiedenen ähnlichen Tools, übernommen. Standardmäßig generiert der Typescript-Transpiler Code in der Version ‚ECMAScript 5‘, jedoch kann optional auch auf ‚ECMAScript 3‘ oder ‚ECMAScript 2015‘ umgestellt werden.
Während Typescript zunächst Ahead Of Time in JavaScript-Code transpiliert wird, wird dieser JavaScript-Code dann meist Just In Time im Browser kompiliert.
Bemerkung
Der Begriff Transpiler weist darauf hin, dass es sich um eine Umwandlung zwischen zwei Programmiersprachen, auf ungefähr dem selben Abstraktionslevel, handelt. Im Gegensatz dazu übersetzen Compiler den Code in ein niedrigeres Abstraktionslevel.
Typ-Definitions-Dateien
Da Typescript die direkte Zusammenarbeit von Typescript-Code und JavaScript-Code ermöglicht, kann man auch externe Bibliotheken nutzen. Diese lassen sich genau so direkt nutzen, wie wenn die Entwicklung in JavaScript stattfinden würde.
Um JS-Bibliotheken in Typescript jedoch mit allen Vorteilen der Typisierung nutzen zu können, ohne diese neu implementieren zu müssen, gibt es die Möglichkeit Definitions-Dateien zu erstellen, welche die nötigen Typdefinitionen für die Nutzung der Bibliothek vorgeben. Diese Definitionen sind für bekannte Bibliotheken, wie jQuery oder Node.js, sowie auch für viele kleinere Projekte, meist von Dritten bereits erstellt, und zur Verfügung gestellt.
DefinitelyTyped
Das GitHub-Repository DefinitelyTyped ist eine klare Empfehlung, um herauszufinden ob für ein bestimmtes Projekt bereits diese Typescript-Definitionen vorhanden sind, um diese in eigene Projekt einbinden zu können. jQuery-Definitions-Datei und Node.js-Definitions-Datei
So sieht beispielsweiße die erste Definition der jQuery-Funktion on() aus:
7613 /**
7614 * Attach an event handler function for one or more events to the selected elements.
7615 * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin".
7616 * @param selector A selector string to filter the descendants of the selected elements that trigger the event. If the
7617 * selector is null or omitted, the event is always triggered when it reaches the selected element.
7618 * @param data Data to be passed to the handler in event.data when an event is triggered.
7619 * @param handler A function to execute when the event is triggered.
7620 * @see \`{@link https://api.jquery.com/on/ }\`
7621 * @since 1.7
7622 */
7623 on<TType extends string,
7624 TData>(
7625 events: TType,
7626 selector: JQuery.Selector,
7627 data: TData,
7628 handler: JQuery.TypeEventHandler<TElement, TData, any, any, TType>
7629 ): this;
Es müssen die verschiedensten Möglichkeiten mit verschiedenen Parametertypen beachtet und extra definiert werden.
Zum Beispiel kann für den Parameter selector auch null und undefined übergeben werden:
7630 /**
7631 * Attach an event handler function for one or more events to the selected elements.
7632 * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin".
7633 * @param selector A selector string to filter the descendants of the selected elements that trigger the event. If the
7634 * selector is null or omitted, the event is always triggered when it reaches the selected element.
7635 * @param data Data to be passed to the handler in event.data when an event is triggered.
7636 * @param handler A function to execute when the event is triggered.
7637 * @see \`{@link https://api.jquery.com/on/ }\`
7638 * @since 1.7
7639 */
7640 on<TType extends string,
7641 TData>(
7642 events: TType,
7643 selector: null | undefined,
7644 data: TData,
7645 handler: JQuery.TypeEventHandler<TElement, TData, TElement, TElement, TType>
7646 ): this;
Wenn beispielsweiße die Verwendung eines Typs veraltet ist, kann sich natürlich auch die zugehörige Dokumentation unterscheiden:
7647 /**
7648 * Attach an event handler function for one or more events to the selected elements.
7649 * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin".
7650 * @param selector A selector string to filter the descendants of the selected elements that trigger the event. If the
7651 * selector is null or omitted, the event is always triggered when it reaches the selected element.
7652 * @param data Data to be passed to the handler in event.data when an event is triggered.
7653 * @param handler A function to execute when the event is triggered.
7654 * @see \`{@link https://api.jquery.com/on/ }\`
7655 * @since 1.7
7656 * @deprecated Deprecated. Use \`{@link JQuery.Event }\` in place of \`{@link JQueryEventObject }\`.
7657 */
7658 on(events: string,
7659 selector: JQuery.Selector | null | undefined,
7660 data: any,
7661 handler: ((event: JQueryEventObject) => void)): this;
[TS] [jQuery] [NodeJS] [DefinitelyTyped] [jQuery-Def] [NodeJS-Def]
1.6. Ansynchrone Programmierung und Event Loop
Browserprogramme sollen performant sein. Eine besondere Rolle spielt hierbei, dass der Benutzer eine Website bedienen kann, während diese lädt und andere Aktionen (parallel dazu) ausführt. Synchrone Programme können Aktionen nur nacheinander abarbeiten und währenddessen auf keine Eingaben reagieren; was das Bedienen einer Website erschweren würde.
Leider führt parallel ausgeführter Code immer auch zu mehr Komplexität. In der Webprogrammierung hat man deshalb folgende Möglichkeiten asynchronen Code zu strukturieren ausprobiert:
Callbacks
Futures
Promises
Async / Await
Hier ein Blogpost der sich mit der Historie von asynchronen Code in Javascipt beschäftigt https://developer.okta.com/blog/2019/01/16/history-and-future-of-async-javascript.
Schlussendlich wurden die async
und await
Keywords in einigen Sprachen eingeführt, so auch in Typescript.
async
und await
sind ein monadisches Sprachkonstrukt
und haben damit einen mathematischen Hintergrund aus der Typetheorie.
Monaden finden Anwendung in der Organisation von Seiteneffekten zu der restlichen Programmlogik.
So ist der Input und Output einer async
function in einem monadischen Kontext,
der in Typescript den Namen Promise
erhalten hat.
Üblicherweise wird in der asnyc
function ein Seiteneffekt gekapselt.
Seiteneffekte können hier das Warten auf die Antwort eines Requests oder auch IO im Allgemeinen sein.
Seiteneffekte sind auf ein Programm wirkende Umwelteinflüsse, z.B. Hardwarebegrenzungen, Zeit, etc..
Hier weiterführende Artikel:
https://en.wikipedia.org/wiki/Monad_(functional_programming)
https://en.wikipedia.org/wiki/Side_effect_(computer_science)
Dieses Programm soll verdeutlichen was die Keywords „async“ und „await“ bezwecken.
// Dieses Programm ist ein Zähler,
// der sich nach einer gewissen Zeit
// immer wieder erhöht.
// Eine function kann async sein.
async function delay(milliseconds: number) {
return new Promise<void>((resolve) => {
setTimeout(resolve, milliseconds);
});
}
async function delayedCounting(pause: number, heading ) {
let num = 0;
while (true) {
await delay(400);
await delay(400);
await delay(400);
// await stößt die Ausführung der function an
// und wartet auf das Ergebnis der function.
// D.h. die aufeinanderfolgenden await Statements
// werden sequenziell nacheinander ausgeführt.
// Effektiv wird hier 3* 400 ms, also 1200 ms, gewartet
message = num.toString();
// auch der synchrone Code hier
// wird sequenziell nach dem letzten
// await Statement abgearbeitet.
heading.textContent = message;
console.log(message);
num +=1;
}
}
let message: string;
let heading = document.createElement('h1');
heading.textContent = message;
document.body.appendChild(heading);
// Normalerweise ist das await Keyword nötig,
// um eine async function auszuführen.
// Eine Ausnahme sind async functions,
// welche im top level eines Typescript
// Programmes aufgerufen werden,
// wie es hier der Fall ist:
delayedCounting(100, heading);
Zwei asynchrone Funktionen werden parallel ausgeführt:
async function slowCounting(pause: number, heading ) {
let num = 0;
while (true) {
await delay(400);
message = num.toString();
heading.textContent = message;
console.log(message);
num +=1;
}
}
async function delay(milliseconds: number) {
return new Promise<void>((resolve) => {
setTimeout(resolve, milliseconds);
});
}
let message: string;
let heading = document.createElement('h1');
let heading2 = document.createElement('h1');
heading.textContent = message;
heading2.textContent = message;
document.body.appendChild(heading);
document.body.appendChild(heading2);
Promise.all([
slowCounting(100, heading), slowCounting(100, heading2)
]);
Hier eine Referenz zu async
functions:
1.7. Die Event Loop
Typescript-Befehle werden in einer Event-Loop ausgeführt.
Dazu ist es wichtig zugrunde liegende Speichermodell im Browser zu verstehen. Der Speicher kann in drei Teile gegliedert werden:
Der Stack, der Ort an dem Funktionsparameter abgelegt werden.
Die Queue, hier werden alle Javascript Befehle vorgehalten, die es noch auszuführen gilt.
Der Heap, wo dynamische Speicherwerte abgelegt werden.
Was sich in der Queue befindet wird durch die Event-Loop abgearbeitet. Die Event-Loop nimmt nach dem FIFO Algorithmus ein Event aus der Queue. Das Event wird dann ausgeführt, es werden z.B. Funktionsparameter auf dem Stack abgelegt und abgearbeitet. Danach wird das nächste Element aus dem Stack genommen. Dieser Ablauf passiert sequenziell in einem Thread.
Eine Ausnahme können hier IO Aktionen darstellen. IO Aktionen müssen nicht die Event-Loop blockieren.
Hier ein Beispiel von einem blockenden Thread:
const syncWait = ms => {
const end = Date.now() + ms
while (Date.now() < end) continue
}
while (true){
syncWait(1000);
console.log("blocking");
}
Hier ein Beispiel von einem nicht blockenden Thread:
async function test() {
while (true){
await delay(100)
console.log("blocking");
}
}
async function delay(milliseconds: number) {
return new Promise<void>((resolve) => {
setTimeout(resolve, milliseconds);
});
}
test()
1.8. Anwendungsbeispiel
1.8.1. Allgemeine Syntax-Beispiele
1// TS unterstützt Tupel und optionale parameter
2type ExampleT = [Number, String, boolean?]
3
4const array: ExampleT = [1, "test", true]
5
6// TS Generics können mehrere Typen beinhalten
7const exampleArray: Array<string | Number[]> = []
8
9array.push(10)
10array.push("string")
11array.push(false)
12
13// TS unterstützt Fallbacks
14const ex = [] ?? "empty array"
15
16// TS unterstützt Interfaces
17interface ExampleI {
18 propertyOne: boolean
19 propertyTwo: String
20 proptertyThree?: ExampleT
21 propertyFour?: () => void
22}
23
24// TS unterstützt Vererbung
25interface ExampleITwo extends ExampleI {
26 additionnalProperty: Array<String>
27}
28
29// TS unterstützt neben Interfaces auch Typen
30type typeA = {
31 prop1: boolean
32}
33
34type typeB = {
35 prop1: boolean
36}
37
38// TS unterstützt das casten von Typen nach truthy/falsy Prinzip
39const blncast: typeA = {prop1: !![]}
40
41// TS unterstützt das Casten von Typen
42const cast: typeB = blncast as typeB
43
44// TS unterstützt Klassen sowie das Implementieren von Interfaces
45// TS unterstützt Datenkapselung wie private, public, readonly
46// Funktionen und Typen können auch Datentypen in Klassen sein
47class ExampleClass implements ExampleI {
48 constructor(string: String){ this.string = string }
49 private readonly string: string
50
51 propertyOne: boolean = false
52 propertyTwo: String = "string"
53 proptertyThree: ExampleT = [1, "string", true]
54 propertyFour: () => void = () => {}
55
56 public getString() {
57 return this.string
58 }
59}
60
61// erstellen einer Instanz mit Constructorparametern
62const ECC = new ExampleClass("parameter for constructor")
63
64// normale Promise variante via Fetch
65fetch("facebook.com/getUser?id=12345").then(res => console.log(res)).catch(e => console.log(e))
66
67// TS unterstützt import/export syntax
68export type AFType = {
69 url: String
70}
71import { AFTType } from 'example.ts'
72
73// TS empfielt Typisierung von Parametern
74// TS unterstützt async - await um asynchronen Code wie Synchronen zu behandeln
75async function asyncFunction(params: AFType) {
76
77 return await fetch(params.url)
78}
79
80console.log(asyncFunction({url: "facebook.com/getUser?id=12345"}))
1.8.2. Beispielklasse anhand eines Websocket Service
1// Beispielklasse aus tatsächlich verwendeten Websocket Service
2import { EMPTY, Subject } from 'rxjs'
3import { catchError, switchAll, tap } from 'rxjs/operators'
4import { WebSocketSubject } from 'rxjs/webSocket'
5import { WebSocketFactory } from 'wsFactory'
6
7export class WebsocketService {
8 private socket$!: WebSocketSubject<string | unknown>
9
10 private messagesSubject$ = new Subject()
11
12 public messages$ = this.messagesSubject$.pipe(
13 switchAll<unknown | any>(),
14 catchError((e) => {
15 throw e
16 })
17 )
18
19 public connect(path: string): void {
20 if (!this.socket$ || this.socket$.closed) {
21 this.socket$ = WebSocketFactory(WSconfig(path))
22 this.socket$.subscribe()
23 const messages = this.socket$.pipe(
24 tap({
25 error: (error: any) => console.log(error),
26 }),
27 catchError((_) => EMPTY)
28 )
29 this.messagesSubject$.next(messages)
30 }
31 }
32
33 sendMessage(message: string): void {
34 this.socket$.next(JSON.stringify(message))
35 }
36
37 close(): void {
38 this.socket$.unsubscribe()
39 this.socket$.complete()
40 }
41}
1.9. Beispielprojekt
1 // api url
2 const api_url: string = "https://reqres.in/api/users"
3
4 type User = {
5 id: string
6 email: string
7 first_name: string
8 last_name: string
9 avatar: string
10 }
11
12 type ApiResponse = {
13 page: number
14 per_page: number
15 total: number
16 total_pages: number
17 data: User[]
18 }
19
20 // Defining async function
21 async function getapi(url: string): Promise<void> {
22
23 // Storing response
24 const response: globalThis.Response = await fetch(url)
25
26
27 if (response) {
28 hideLoader()
29 }
30 // Storing data in form of JSON
31 show(await resJson(response))
32 }
33
34 async function resJson(data: globalThis.Response): Promise<ApiResponse> {
35
36 return await data.json()
37
38 }
39
40 // Function to hide the loader
41 function hideLoader(): void {
42 document.getElementById('loading').style.display = 'none'
43 }
44 // Function to define innerHTML for HTML table
45 function show(data: ApiResponse): void {
46 let tab =
47 `<tr>
48 <th>Id</th>
49 <th>Email</th>
50 <th>First name</th>
51 <th>Last name</th>
52 </tr>`
53
54 // Loop to access all rows
55 for (let r of data.data) {
56 tab += `<tr>
57 <td>${r.id} </td>
58 <td>${r.email}</td>
59 <td>${r.first_name}</td>
60 <td>${r.last_name}</td>
61 </tr>`
62 }
63 // Setting innerHTML as tab variable
64 document.getElementById("user").innerHTML = tab
65 }
66
67 // Calling that async function
68 getapi(api_url)
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <script src="script.ts"></script>
5 <link rel="stylesheet" href="style.css" />
6 <meta charset="UTF-8" />
7 <meta name="viewport"
8 content="width=device-width, initial-scale=1.0" />
9 <title>Document</title>
10 </head>
11 <body>
12 <!-- Here a loader is created which
13 loads till response comes -->
14 <div class="d-flex justify-content-center">
15 <div class="spinner-border"
16 role="status" id="loading">
17 <span class="sr-only">Loading...</span>
18 </div>
19 </div>
20 <h1>Registered Users</h1>
21 <!-- table for showing data -->
22 <table id="user"></table>
23 </body>
24 </html>