# [JavaScript] Projekt: JavaScript-Thread



## Parantatatam (24. Juni 2013)

Hallo Tutorianer,

ich bastle seit einiger Zeit an einem kleinen Projekt, welches die Möglichkeit bietet in JavaScript Threads zu erstellen, ohne dass man dafür existierende Dateien verwenden muss (wie es bei WebWorkern normal ist). Dafür setze ich auf WebWorker und BLOBs. Außerdem werden die Threads in Gruppen zusammen gefasst, so dass man diese gesammelt steuern kann.

So erstellt man bei mir einen Thread:

```
var thread = new Thread(function ( data ) {
  var num = data * 1; // convert to integer
  return ( num * num ); // square of the passed value
});
```
Das war alles (ja, in diesem Fall sind Threads eher unnötig, aber es reicht als Beispiel). Möchte man jetzt einen Wert an den Thread senden, so geht dies wie folgt:

```
thread.send( 5, function ( ret ) {
  console.log( ret ); // prints 25
});
```
Aus meiner Sicht ist dies viel eleganter als alles, was man direkt mit WebWorkern an stellen kann.

Ich würde gerne von euch wissen, was ihr darüber denkt, was man noch verbessern könnte (oder optimieren) und was vielleicht noch nützlich an Funktionen wäre.

Das Projekt findet ihr bei GitHub mit einer Dokumentation und Skripten sowohl unkomprimiert (rein und für require.js) und komprimiert (35 %): https://github.com/MeiKatz/javascript-thread


----------



## CPoly (24. Juni 2013)

Ist dir https://github.com/adambom/parallel.js bekannt?


----------



## Parantatatam (24. Juni 2013)

Nein, war es mir bisher nicht. Ich habe, bevor ich mit meinem Projekt angefangen habe, auch nach solchen Projekten gesucht, jedoch immer nur – aus meiner Sicht – sehr unzureichende gefunden. Das von dir verlinkte Projekt, kommt meinen aber sehr nahe, auch wenn es einige Unterschiede gibt. Aber gut zu wissen, dass noch andere so denken wie ich.


----------



## ComFreek (24. Juni 2013)

Hi einfach nur crack,

die Idee finde ich sehr interessant!
Ich werde definitiv demnächst mal reinschauen


----------



## Parantatatam (24. Juni 2013)

@ComFreek: Mach das mal. Falls meine Erläuterungen bei GitHub nicht ausreichend sollte, könnt ihr natürlich bei mir nachfragen – also besser: ihr sollt.


----------



## ComFreek (25. Juni 2013)

Was nach einem Tag daraus geworden ist?

Ein fast komplettes Rewrite in TypeScript, inkl. QUnit Test Cases 
https://github.com/ComFreek/ThreadLibrary.js

Die API ist soweit ziemlich ähnlich. Ein Ding habe ich jedoch gravierend geändert:

```
var funcBody = func.toString().substring(rawFuncCode.indexOf("{") + 1, rawFuncCode.lastIndexOf("}"));
```
Dies sollte zuverlässiger als deins arbeiten:

```
var code = callback.toString().match( /^function\s*\([^\)]*\)\s*\{\s*((\S|\s)*\S)\s*\}$/ );
```
mit welchem ich bei Newlines Probleme bekommen hatte.

Außerdem habe ich Thread und ThreadGroup vollständig unabhängig  voneinander erstellt und in 2 Dateien ausgelagert.


----------



## Parantatatam (25. Juni 2013)

@ComFreek: Ich finde deine Umsetzung sehr gelungen und werde mich von deiner Umsetzung inspirieren lassen, also was das Erfassen des Codes der Funktion betrifft. Dies war ursprünglich wesentlich nötiger, da ich nicht nur Callback-Funktionen akzeptiert habe, sondern auch HTML-Script-Elemente. Da ich diese aber entfernt habe, dient es momentan nur noch zum Validieren, also um zu ermitteln, ob in der Funktion überhaupt etwas steht.
Das Problem, was du bei dir erzeugst, ist, dass du innerhalb des Threads immer den Variablennamen "data" für die übergebenen Werte nutzen musst.

Außerdem finde ich es eine gute Idee, dass du Thread und ThreadGroup getrennt hast. Da ich bei mir aber erzwinge, dass ein Thread immer zu einer (und zwar genau zu einer) Gruppe gehört, kann ich diese Klassen zwar in unterschiedliche Dateien auslagern, aber ihre Abhängigkeit bleibt dennoch erhalten.

Übrigens habe ich meine Klassen auch noch etwas modifiziert, da ich bemerkt habe, dass es auch Situationen gibt, in denen ein Thread nicht nur einmal am Ende einen Wert ausgibt, sondern auch Situationen, in denen dies kontinuierlich der Fall ist, ohne dass man den Thread jedes Mal von außen starten will. Daher gibt es bei mir jetzt auch eine Methode "send", die man innerhalb eines Threads ausführen kann, um Daten zu übertragen. Und da dadurch auch nicht terminierende Threads entstehen können, gibt es passend dazu eine Methode "stop", welche man sowohl auf einen Thread als auch auf eine ThreadGroup ausführen kann und somit diese Threads in einen Wartezustand versetzt. Vorher ging das bloß mit "kill", aber damit beendet man den Thread komplett.

PS: Wo kann man den TypeScript überhaupt anwenden? Es ist natürlich wesentlich eleganter als nur JavaScript, da man hier auch echte private Variablen hat, aber ich frage mich schon die ganze Zeit, wie es mit der Kompatibilität in den Browsern aussieht.


----------



## ComFreek (26. Juni 2013)

einfach nur crack hat gesagt.:


> Das Problem, was du bei dir erzeugst, ist, dass du innerhalb des Threads immer den Variablennamen "data" für die übergebenen Werte nutzen musst.



Stimmt, muss mal schauen, wie man das lösen könnte.
Ist in der neusten Version gefixt: https://github.com/ComFreek/ThreadLibrary.js?source=cc
Nun wird z.B. folgender Code erzeugt:

```
this.addEventListener("message", function(evt) {
  var ret = ([rawFuncCode]).call(evt.target, evt.data);
  this.postMessage(ret);
}, false);
```
Wobei [rawFuncCode] die ganze Funktion inkl. function-Keyword und Parameterliste ist, z.B.:

```
function test(myData) {}
```

Deine Änderungen habe ich schon gesehen  Es gibt nun sozusagen eine 1 zu 1 Kommunikation zwischen Thread und "Hauptthread".




> PS: Wo kann man den TypeScript überhaupt anwenden? Es ist natürlich wesentlich eleganter als nur JavaScript, da man hier auch echte private Variablen hat, aber ich frage mich schon die ganze Zeit, wie es mit der Kompatibilität in den Browsern aussieht.


TypeScript transkompiliert TypeScript zu JavaScript.
Man schreibt TypeScript, bekommt dabei alle Vorteile der zusätzlichen Syntax, etc. und am Ende bekommt man dennoch EcmaScript 3 oder 5 (je nach Option) JavaScript, welches überall unterstütz wird 

Installier dir einfach mal TypeScript: http://www.typescriptlang.org/#Download

Dann lade dir einfach mal mein Code herunter und führe build.ps1 oder build.sh aus.

Danach ist im Ordner build/ die Datei all.js vorhanden. Die enthält ganz normales JS.


----------



## Parantatatam (26. Juni 2013)

ComFreek hat gesagt.:
			
		

> Deine Änderungen habe ich schon gesehen  Es gibt nun sozusagen eine 1 zu 1 Kommunikation zwischen Thread und "Hauptthread".


Wenn man so möchte, ja. Man kann sich in der entsprechenden README-Datei auch ansehen, wozu dies beispielsweise gut sein könnte.

Außerdem lösche ich in dem Thread-Code noch die Eigenschaften "onmessage", "onerror" und "self", um den direkten Zugriff auf den Worker zu verhindern. Es gab auch die Überlegung, ob ich Eigenschaft "location" mit jener des Skript überschreibe, welches den Thread erstellt hat. Allerdings wurden dabei die Thread-Skripts immer wesentlich größer als das Skript, welches eigentlich ausgeführt werden sollte (also im Vergleich zu meinen Testskripts) Und was ich fast vergessen hatte: die Threads kennen jetzt ihre eigene ID – vielleicht könnte das irgendwann mal nützlich sein.

PS: TypeScript werde ich mir mal anschauen. Es scheint so etwas ähnliches wie Dart und CoffeeScript zu sein.


----------



## ComFreek (26. Juni 2013)

einfach nur crack hat gesagt.:


> Außerdem lösche ich in dem Thread-Code noch die Eigenschaften "onmessage", "onerror" und "self", um den direkten Zugriff auf den Worker zu verhindern. Es gab auch die Überlegung, ob ich Eigenschaft "location" mit jener des Skript überschreibe, welches den Thread erstellt hat.



Wieso verweigerst du den direkten Zugriff? Ich find's immer schön, wenn man so viel Freiheiten wie möglich auf die native Schnittstelle trotz Framework/Bibliothek hat.
Was soll denn die Eigenschaft "location" besitzen?


PS: Genau, sowas ähnliches.

PSS: Schon 89 Commits  Respekt.


----------



## Parantatatam (26. Juni 2013)

Naja, also das Problem was ich sehe, besteht darin, dass ich damit die Möglichkeit für unerwartetes Verhalten eröffne Und das ist mir eher unlieb. Außerdem würde ich durch eine Änderung von "location" bewirken, dass der Thread beim Zugriff darauf glauben würde, dass er in dem aufrufenden Skript läuft, und nicht in dem BLOB-Objekt, in dem er läuft.

Zu deinem PPS: Ich reiche immer gleich ein, wenn ich etwas gefunden habe, was mir aufgefallen ist, so dass ich es nicht wieder vergesse. Wenn ich mit anderen zusammen arbeiten würde, würde ich da vermutlich anders arbeiten.


----------



## ComFreek (26. Juni 2013)

Ich weiß nicht ob du dich mit Microsoft's Promise-Objekten in der Windows 8 JS-Lib auskennst. Jedenfalls bieten diese Callback-Funktionen für Progress und Finish-Events an 

Diese habe ich im Prinzip bei mir jetzt auch drinne:

```
var thread = new TL.Thread(function (myData) {
  // build and send powers of myData
  for (var i=0; i<5; i++) {
    send(Math.pow(myData, i));
  }
  // send/return last power (=5)
  return Math.pow(myData, i+1);
});

thread.send(5,
  function progress (retVal) {
    log('Got value "' + retVal + '".');
  },
  function finish (retVal) {
    log('Finished with value "' + retVal + '"');
  }
);
```

Alternativ kann man auch nur eine Funktion an TL.Thread::send() übergeben:

```
thread.send(5,
  function progressAndFinish (retVal, finished) {
    log('Got value "' + retVal + '".');
    if (finished) {
      log('Finished now');
    }
  }
);
```


----------



## Parantatatam (26. Juni 2013)

Das ist eine hübsche Idee und soweit habe ich noch gar nicht gedacht, da ich die Idee mit dem permanenten Senden und der Möglichkeit einen Thread zu stoppen, erst gestern hatte. Aber das wäre letztendlich die logische Folge, die dazu nicht einmal meinen Ideen widerspricht: top!


----------



## ComFreek (28. Juni 2013)

Ich habe jetzt noch eine Idee verwirklicht: Import von lokalen Funktionen.

Beispiel:

```
// will be imported into our thread
function square(i) {
	return i*i;
}

var thread = new TL.Thread();

thread.setFunction(function (myData) {
	return square(myData);
}, [square]);

thread.run();

thread.send(5, function (retVal) {
	alert(retVal); // should alert 25
	// Destroy thread (i.e. free memory for BLOB url object)
	this.destroy();
});
```

Allerdings müssen die Funktionen wirklich mit "function" deklariert werden und dürfen nicht an eine Variable praktisch nur gebunden sein:

```
function x() {} // works
var x = function y() {} // doesn't work
```


----------



## Parantatatam (28. Juni 2013)

Die Idee mit der destroy-Methode ist durchaus sinnvoll – daran habe ich noch gar nicht gedacht, dass es natürlich auch sinnvoll ist, dass ein Thread erst "gekillt" wird, wenn der Thread ausgeführt wurde. Aber da ich das zufälligerweise schon implementiert habe, passt das auch wieder  Und die Idee mit dem Importieren der lokalen Funktionen finde ich auch sehr interessant. Ich werde mal schauen, wie ich das bei mir implementieren werde. Die Funktion zum Minimieren von Funktionen habe ich ja bereits in meiner letzten Version (0.3.6) implementiert, so dass ich dies auch auf andere Funktionen implementieren kann.

Nachtrag: Ich habe das Laden der lokalen Funktionen jetzt mal übernommen (also die Idee), und das sieht jetzt bei mir so aus (ich unterscheide nicht zwischen einer Pfadangabe zu einer Datei oder einem Funktionsobjekt):

```
if ( require.length > 0 ) {
  var local = [],
      files = [],
      key;

  for ( key in require ) {
    if ( typeof require[ key ] === "string" ) {
      files.push( require[ key ] );
    } else if ( typeof require[ key ] === "function" && /^function[ ]+\w+/.test( require[ key ].toString() ) ) {
      local.push( minimize( require[ key ].toString() ) );
    } else {
      throw new ThreadError( "could not load required script or function" );
    }
  }

  if ( local.length > 0 ) {
    code = local.join( ";" ) + ";" + code;
  }

  if ( files.length > 0 ) {
    code = "importScripts(\"" + files.join( "\",\"" ) + "\");" + code;
  }
}
```


----------



## ComFreek (28. Juni 2013)

einfach nur crack hat gesagt.:


> Die Funktion zum Minimieren von Funktionen habe ich ja bereits in meiner letzten Version (0.3.6) implementiert, so dass ich dies auch auf andere Funktionen implementieren kann.



Stimmt, da wollte ich dich fragen: wozu minimieren? Heutige Browser sind doch sehr optimiert im Hinblick auf JS-Ausführung.

*Nachtrag*

Array.join statt einer Schleife zu nutzen ist natürlich sehr elegant, da JS zum Glück alle Elemente zuerst in Strings umwandelt. Hab ich gleich mal übernommen 

Wobei man externe Dateien auch innerhalb eines Webworkers standardmäßig laden kann, sprich dein's ist nur eine Verinfach der API seitens des Clienten?


----------



## Parantatatam (28. Juni 2013)

Ich minimiere es, damit die temporären Skripte eine möglichst geringe Größe aufweisen. Davon erhoffe ich mir, dass es den Cache des Nutzers nicht überlastet. Es kann allerdings auch sein, dass ich mich darüber zu sehr sorge, und das es eigentlich egal ist. Falls letzteres der Fall sein sollte, habe ich immerhin mal eine Funktion in JavaScript geschrieben, die JavaScript-Funktionen minimiert


----------



## Parantatatam (28. Juni 2013)

Genau, ich habe die API ein bisschen unterteilt: einerseits kann man Skripts, die *davor* geladen werden sollen, als Option angeben, andererseits kann man mit der Methode import() innerhalb des Threads noch Skripte nachladen. Da ich jetzt aber auch lokale Funktionen davor laden kann, hat diese Möglichkeit noch eine zweite Legitimation bekommen. Ich vermute, dass die Methode import() kaum verwendet werden wird, daher ist sie nur zur Sicherheit da, falls doch einmal jemand ..


----------



## ComFreek (28. Juni 2013)

einfach nur crack hat gesagt.:


> [...] habe ich immerhin mal eine Funktion in JavaScript geschrieben, die JavaScript-Funktionen minimiert


Grund genug  Der TypeScript-Compiler ist übrigens vollständig selbst in TypeScript/JavaScript geschrieben (deswegen auch so langsam -.-).

Mittlerweile kann ich auch an Variablen gebundene und anonyme Fkt. importieren:

```
// will be imported into our thread
function square(i) {
	return i*i;
}

// "variable bound" function
var square2 = function (i) {
	return i*i;
}

var thread = new TL.Thread();

thread.setFunction(function (myData) {
	return square(myData) / square2(myData);
	// this should always return 1!
}, [square, {name: "square2", func: square2}] );
```

Findest du dieses hier (rein stilistisch)

```
[square, {name: "square2", func: square2}]
```
oder dieses hier besser?

```
{"square": square, "square2": square2}
```

Dies macht es übrigens auch möglich, Funktionen sozusagen umzubenennen innerhalb des Threads:

```
[{name: "square123", func: square}]
```


----------



## Parantatatam (28. Juni 2013)

Ich persönlich finde die zweite Variante schöner, weshalb ich auch selber gerade dabei bin, dafür eine Lösung zu finden. Allerdings wird das Skript dadurch immer komplexer. Nun gut, solange ich da noch durchsehe, sollte das aber auch machbar sein  Ansonsten ist mir auch schon aufgefallen, dass die Möglichkeit einer Umbenennung durchaus reizvoll ist. Abgesehen davon, ist die zweite Variante um einiges kürzer und daher für die Entwicklung durchaus praktischer.

```
var thr = new Thread([ "foo.js", "bar.js", square, { "square2": square2 }], ... );
```


----------



## ComFreek (29. Juni 2013)

einfach nur crack hat gesagt.:


> ```
> var thr = new Thread([ "foo.js", "bar.js", square, { "square2": square2 }], ... );
> ```



Hiermit beziehst du dich wohl aber auf die erste Variante von mir? 

Ich habe gerade probiert, das Importieren von externen Dateien auch zu erlauben.
In meiner Schleife, die über das übergebene Array iteriert:

```
// import external file
else if (typeof elem == "string") {
  code += "importScripts(\"" + elem + "\");\n";
}
```
Beim Ausführen des Codes (sowohl lokal als auch auf einem Server) bekomme ich immer folgenden Fehler:


> Uncaught Error: SyntaxError: DOM Exception 12


Dieser tritt in der ersten Zeile des generierten WebWorker Codes auf, Beispiel:

```
importScripts("external.js"); // <----- genau hier
var send = function (data) { this.postMessage({data: data}); };this.addEventListener("message", function(evt) {var ret = (function (myData) {
		return square(myData);
	}).call(evt.target, evt.data);this.postMessage({data: ret, finished: true});}, false);
```

Weißt du, um was es sich da handelt?


----------



## Parantatatam (29. Juni 2013)

Ja, weiß ich. Ich vermute, dass du in dem Skript irgendwo auf die DOM-API zugreifst, was Threads ausdrücklich verboten ist. Dementsprechend wird dort eine Exception geworfen.


----------



## ComFreek (29. Juni 2013)

einfach nur crack hat gesagt.:


> Ja, weiß ich. Ich vermute, dass du in dem Skript irgendwo auf die DOM-API zugreifst, was Threads ausdrücklich verboten ist. Dementsprechend wird dort eine Exception geworfen.



Ne, das mach ich definitiv nicht 
Der Fehler tritt auf, sobald ich auch nur die erste Zeile reinpacke mit importScripts().


----------



## Parantatatam (29. Juni 2013)

Mh, ich hatte diesen Fehler bisher nur einmal, weil ich versucht habe jQuery zu importieren, und das baut logischerweise auf die DOM-API.


----------



## ComFreek (29. Juni 2013)

Bin gerade auf diese Seite gestoßen, welche folgendes schreibt:


> A web worker can load additional scrips with importScripts(URL, ...). The URLs can be relative and, if so, are *relative to the file doing the importing*.


Relativ zu der WebWorker-Datei also. Nun haben wir ja überhaupt keine richtige Datei, sondern ein Blob-Objekt.
Das wirds wahrscheinlich sein!

Hast du bei dir schonmal Datei-Import getestet?

*EDIT*
Ja, das war es in der Tat. Als ich eine absolute URL auf der gleichen Domain angegeben habe, funktionierte der Import tadellos!!


----------



## Parantatatam (30. Juni 2013)

Heißt das, dass eine URI mit führendem Slash bereits reicht, damit diese als absolute Pfadangabe gilt? Oder muss da auch die Domain mit angegeben werden?


----------



## ComFreek (1. Juli 2013)

einfach nur crack hat gesagt.:


> Heißt das, dass eine URI mit führendem Slash bereits reicht, damit diese als absolute Pfadangabe gilt? Oder muss da auch die Domain mit angegeben werden?



Habs gerade erneut ausprobiert: Fehlanzeige.

Man darf überhaupt *keine relative* URI benutzen.


----------



## Parantatatam (1. Juli 2013)

Also ich habe es jetzt so gelöst, dass ich alle relativen Pfadangaben in absolute umwandle:

```
function absolute ( path, base ) {
    var parts = path.split( "/" ),
        stack = [],
        i;

    for ( i = 0; i < parts.length; ++i ) {
        if ( parts[ i ] === ".." ) {
            stack.pop();
        } else if ( parts[ i ] !== "." ) {
            stack.push( parts[ i ] );
        }
    }

    path = stack.join( "/" ).replace( /^[\/]+/, "" );
    return ( base == null ? "/" + path : base + "/" + path );
}
```


----------



## ComFreek (1. Juli 2013)

Jap, das ist mir auch schon eingefallen, doch die Eleganz, die man normalerweise bei relativen Angaben hätte, fehlt einfach.
Es gibt ja noch nicht mal Konstanten wie __FILE__ oder __DIR__ wie in PHP, sodass man den Pfad, wenn man die Datei verschiebt, manuell anpassen muss.

PS: Wieso eröffnet auf einmal jeder einen Thread im JS-Forum mit [ js ]?!


----------



## Parantatatam (1. Juli 2013)

Also ich bin gerade dabei das Problem so zu lösen, dass ich ein weiteres Skript habe, welches in jedem Thread über importScripts() geladen wird, und welche beim Erstellen des Skripts die Pfadangabe erhält und somit damit arbeiten kann. Ist zwar etwas umständlich, aber es funktioniert.

Zu deinem PS: Ich mach das schon immer so, weil ich zwischen JavaScript und jQuery unterscheide – ist zwar immer JavaScript, aber jQuery-Fragen unterscheiden sich doch gewaltig vom reinem JavaScript.


----------



## ComFreek (1. Juli 2013)

einfach nur crack hat gesagt.:


> Also ich bin gerade dabei das Problem so zu lösen, dass ich ein weiteres Skript habe, welches in jedem Thread über importScripts() geladen wird, und welche beim Erstellen des Skripts die Pfadangabe erhält und somit damit arbeiten kann. Ist zwar etwas umständlich, aber es funktioniert.



Heißt dass, dass du importScripts() im eingebundenen Skript aufrufst und es dem Browser so möglich ist, eine relative Angabe zu verarbeiten.

PS: Ja klar  ok.


----------

