Dein Problem ist nicht, dass eine Funktion in einer anderen Funktion
ist, sondern dass die zweite Funktion asynchron (also später) aufgerufen
wird.
Die erste Funktion ist zu dem Zeitpunkt, wo die andere aufgerufen wird,
schon längst wieder beendet. Ein äquivalentes Szenario zum
veranschaulichen hier:
https://jsfiddle.net/5c7ue1vg/2/
1 | function x(){
|
2 | console.log("function x called");
|
3 | console.log("Asynchronousely calling y");
|
4 | setTimeout(function y(){
|
5 | console.log("function y called");
|
6 | },0);
|
7 | console.log("returning from x");
|
8 | }
|
9 | console.log("Calling x");
|
10 | x();
|
Bei einer Funktion, die längst zurückgekehrt/durchgelaufen/beendet ist,
kann man natürlich nichts mehr zurück geben. Aber die 2te Funktion, kann
eine Funktion aufrufen, die der ersten übergeben wurde. Eine solche
Funktion nennt man deshalb callback funktion (Rückruffunktion).
1 | function x(callback){
|
2 | console.log("function x called");
|
3 | console.log("Asynchronousely calling y");
|
4 | setTimeout(function y(){
|
5 | console.log("function y called");
|
6 | console.log("Calling callback function");
|
7 | callback(null, "something something");
|
8 | },0);
|
9 | console.log("returning from x");
|
10 | }
|
11 | console.log("Calling x");
|
12 | x(function z(err, result){
|
13 | console.log("Function z here, I got called! I got:", result);
|
14 | });
|
Das pattern, bei Callbacks fehler, dann resultat als Argument
mitzugeben, und den Letzten Parameter ein Callback zu machen, ist nur
eine Konvention, die sich früher mal durchgesetzt hat. Es hat den
Vorteil, dass man nicht ständig bei jeder Funktion nachsehen muss, wie
man den callback übergeben muss, wie man Fehler erkennt, etc. Es ist
aber nicht zwingend.
Das Problem daran ist halt, dass man die Funktionen auseinandernehmen
und / oder verschachteln muss, Dinge in callbacks verschieben halt...
Eine erste Verbesserung gab es dann mit Promises.
1 | function x(){
|
2 | console.log("function x called");
|
3 | let result = new Promise(function q(resolve, reject){
|
4 | console.log("Asynchronousely calling y");
|
5 | setTimeout(function y(){
|
6 | console.log("function y called");
|
7 | console.log("Calling callback function");
|
8 | resolve("something something");
|
9 | },0);
|
10 | });
|
11 | console.log("returning from x");
|
12 | return result;
|
13 | }
|
14 | console.log("Calling x");
|
15 | let result = x();
|
16 | console.log("Lets let it call a callback once we actually have a result (the promise is fulfilled)");
|
17 | result.then(function z(result){
|
18 | console.log("Function z here, I got called! I got:", result);
|
19 | });
|
20 | console.log("Keep in mind, here it wasn't called yet!");
|
Ein Promise (=Versprechen) Objekt verlagert, woher das Callback kommt.
Man muss es der Funktion nicht mehr direkt mit geben. Statdessen ist das
Promise Objekt stellvertretend für ein zukünftiges Resultat, oder einen
zukünftigen Fehlschlag da. Da das Promise Objekt in der ersten Funktion
erstellt wurde, kann es von dieser zurückgegeben werden. Und mit .then
und einer callback funktion kann man dann auf ein Ergebnis, oder einen
fehler warten. Wobei es für letzteres noch die .catch Methode gibt.
Jede Funktion die die callback Konvention nutzt kann man so in ein
Equivalent mit promises überführen. Ein Vorteil der Promises ist noch,
dass beim callback der .then Methode, diese ein Resultat und sogar
wieder eine Promise zurückliefern kann, und die .then Methode selbst
auch eine Promise zurück liefert, die dann stellvertretend für dieses
neue Resultat steht. Damit kann man aus einer Verschachtelung eine Kette
machen.
Also z.B. aus dem hier: (callback style)
1 | function c_hallo(callback){
|
2 | callback(null, "Hallo")
|
3 | }
|
4 |
|
5 | function c_martin(str, callback){
|
6 | callback(null, str + " Martin");
|
7 | }
|
8 |
|
9 | function c_uppercase(str, callback){
|
10 | callback(null, str.toUpperCase());
|
11 | }
|
12 |
|
13 | c_hallo(function(err, result){
|
14 | c_martin(result, function(err, result){
|
15 | c_uppercase(result, function(err, result){
|
16 | console.log(result);
|
17 | })
|
18 | });
|
19 | });
|
Kann man das hier machen: (promise style)
1 | function p_hallo(callback){
|
2 | return new Promise(function(resolve){
|
3 | resolve("Hallo");
|
4 | });
|
5 | }
|
6 |
|
7 | function p_martin(res){
|
8 | return new Promise(function(resolve){
|
9 | resolve(res + " Martin");
|
10 | });
|
11 | }
|
12 |
|
13 | function p_uppercase(res){
|
14 | return new Promise(function(resolve){
|
15 | resolve(res.toUpperCase());
|
16 | });
|
17 | }
|
18 |
|
19 | p_hallo()
|
20 | .then(p_martin)
|
21 | .then(p_uppercase)
|
22 | .then(console.log);
|
Btw. in diesem speziellen fall könnte man das auch kürzer schreiben:
1 | Promise.resolve("Hallo")
|
2 | .then(res => res + " Martin")
|
3 | .then(res => res.toUpperCase())
|
4 | .then(console.log);
|
Weil eigentlich alle diese 2 Konventionen so konsequent durchziehen,
gibt es in node convinience Funktionen, um bestehende Funktionen in die
ander Variante zu überführen, nähmlich die Funktionen util.promisify und
util.callbackify. util.promisify(c_hallo) liefert eine Funktion zurück,
die equivalent zu p_hallo. Und util.callbackify(p_hallo) liefert eine
Funktion zurück, die equivalent zu c_hallo ist. Wenn man also eine
Variante hat, aber die andere braucht, nimmt man einfach diese 2
Hilfsfunktionen.
Mit ES7 und async await wurde das dann weiter vereinfacht. async await
sind Syntax Konstrukte um une die ganzen .then und separate Callbacks
auszukommen. Damit sieht eine asynchrone Funktion wieder fast genau so
aus, wie eine Synchrone.
Die p_uppercase Funktion kann mit async wie folgt geschrieben werden:
1 | async function p_uppercase(res){
|
2 | return res.toUpperCase();
|
3 | }
|
Das coole daran sieht man aber, wenn man die .then chain von oben
schreibt:
1 | async function x(){
|
2 | var resultat;
|
3 | resultat = await p_hallo();
|
4 | resultat = await p_martin(resultat);
|
5 | resultat = await p_uppercase(resultat);
|
6 | console.log(resultat);
|
7 | }
|
8 | x();
|
Oder auch:
1 | async function x(){
|
2 | console.log( await p_uppercase( await p_martin( await p_hallo() ) ) );
|
3 | }
|
Mit await "wartet" man quasi also auf das Resultat einer Promise, und
wenn es ankommt, geht's weiter. Es gibt nur 2 wichtige Unterschiede die
man bei Async funktionen beachten muss:
1) async funktionen können überal dort, wo await steht, "unterbrochen"
werden. Das heisst, andere async & callback functionen könnten an den
Stellen wärend dem Warten auf das Ergebnis ausgeführt werden.
2) Man darf nicht vergessen, das await überall hinzuschreiben, wo man
das Resultat einer Promise braucht.