Dodwin: Funktion mit dynamischer Anzahl an Parameter aufrufen

Hallo.

Ich habe folgende Funktion erstellt:

function ajax(file, func_target, param1, param2, param3) {
  var tmp_ajax_obj = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
  tmp_ajax_obj.open('post', file);     // post => no browser-cache
  tmp_ajax_obj.onreadystatechange = function(){
    if(tmp_ajax_obj.readyState == 4)
      if (func_target)
        window[func_target](tmp_ajax_obj.responseText, param1, param2, param3);
  }
  tmp_ajax_obj.send(null);
}

Diese Funktion macht einen Request an die angegebene Adresse (file) und sendet die Antwort an die angegebene Funktion (func_target).
Dabei können derzeit bis zu 3 Parameter übergeben werden, die anschließend an die Zielfunktion weitergereicht werden.
Nun würde ich es aber gerne "sauber" machen und beliebig viele Paramter zulassen.

Ich habe nur leider keine Idee, wie ich das (ohne eval?) lösen könnte.

Verbesserungen an der Funktion sind auch willkommen.

Danke und ein schönes Wochenende,
Dodwin

--
Dodwin
  1. Hi,

    Nun würde ich es aber gerne "sauber" machen und beliebig viele Paramter zulassen.

    Ich habe nur leider keine Idee, wie ich das (ohne eval?) lösen könnte.

    http://de.selfhtml.org/javascript/objekte/function.htm

    MfG ChrisB

    --
    Light travels faster than sound - that's why most people appear bright until you hear them speak.
    1. Hi.

      »» http://de.selfhtml.org/javascript/objekte/function.htm

      Da war ich bereits.
      Wäre schön, wenn du mir noch sagst, wie mir das weiterhelfen soll...

      Das Auslesen der Parameter mittels arguments[x] ist kein Problem (hätte ich vielleicht noch erwähnen sollen).
      Aber wie hänge ich diese an die aufzurufende Funktion?

      Gruß,
      Dodwin

  2. Nun würde ich es aber gerne "sauber" machen und beliebig viele Paramter zulassen.

    Man würde dieses Problem in JavaScript nicht so umsetzen. Das ist keine Lösung, die die Sprachfeatures benutzt. Keine Ajax-Bibliothek arbeitet so.

    Erst einmal würde man eine Callback-Funktion übergeben, keinen Funktionsnamen, denn das hat viele Vorteile. Da JavaScript eine funktionale Sprache ist und jede Funktion ein vollwertiges Objekt, übergibt man Referenzen auf Funktionsobjekte. Die müssen nicht notwendig global sein, sondern können irgendwo anders hängen, lokal und sogar ohne Namen (anonym) als Funktionsausdrücke notiert sein. So kann man es vermeiden, fünfhundert globale Funktionen zu notieren, sondern kann mit Kapselung und Namespaces arbeiten.

    Jetzt ist die Frage, wie man diesen Callback-Funktionen gewisse Variablen zur Verfügung stellt. Auch hier würde ich zu einer funktionale Lösung raten. JavaScript bietet Closures, d.h. verschachtelte Funktionen »erinnern« sich an die lokalen Variablen der Funktion, in der sie notiert wurden:

    function request (p1, p2, p3) {  
       var a = 1, b = 2, c = 3;  
       ajax('/file.txt', function (response) {  
          /* Closure, schließt p1, p2, p3, a, b und c ein */  
          alert(response + "\n" + a + "\n" + b + "\n" + c);  
       });  
    }
    

    Wenn du es etwas geordneter willst, kannst du mit Currying arbeiten. Dann kann die Verschachtelung wegfallen:

    function handler (a, b, c, response) {  
       alert(response + "\n" + a + "\n" + b + "\n" + c);  
    }  
    function request () {  
       var a = 1, b = 2, c = 3;  
       ajax('/file.txt', handler.curry(a, b, c));  
    }
    

    Dazu bräuchtest du diese Helferfunktion, die alle Funktionsobjekte um die Methode curry erweitert:

    Function.prototype.curry = (function (slice) {  
       return function () {  
          var func = this, args = slice.apply(arguments);  
          return function () {  
             return func.apply(this, args.concat(slice.apply(arguments)));  
          };  
       };  
    })(Array.prototype.slice);
    

    Bei dieser Standard-Currying-Funktion wird der Parameter response hinten drangehangen - man könnte ihn auch vorne anhängen (statt concat die arguments-Liste von hinten durchlaufen und alle Elemente mit unshift in args einfügen).

    So wäre es meiner Meinung nach »sauber« - deine gegenwärtige Lösung fühlt sich eher nach PHP an (< 5.3 ;-)).

    apply und arguments sind auf jeden Fall die Objekte, die dir hier weiterhelfen. Damit könntest du dein Problem auch lösen, ohne die vorgeschlagenen Änderungen vorzunehmen.

    // Wandle arguments-Objekt in einen Array um und extrahiere die Parameter 3-n  
    var args = Array.prototype.slice.call(arguments, 2);  
    // Füge vorne einen Parameter ein, nämlich den responseText  
    args.unshift(tmp_ajax_obj.responseText);  
    // Rufe Funktion auf mit dem erzeugten Array als Parameterliste  
    window[func_target].apply(window, args);
    

    Das »Durchreichen« der Parameter funktioniert hier ähnlich wie beim Currying.

    Mathias

    1. Hallo molily,

      Wow! Vielen Dank für die ausführliche Beschreibung.
      Ich werd's mir wohl nochmal in Ruhe durchlesen müssen um alles nachzuvollziehen.

      So wäre es meiner Meinung nach »sauber« - deine gegenwärtige Lösung fühlt sich eher nach PHP an (< 5.3 ;-)).

      Erwischt... ;)

      Gruß,
      Dodwin

      --
      Dodwin
    2. Hallo molily,

      Ok, nach mehrfachem Durchlesen hab ich's jetzt halbwegs verstanden.

      In deinem Beispiel ermöglicht das Currying, dass man der Funktionsreferenz auch später noch Parameter anhängen kann, wenn ich das richtig verstanden habe.

      Bei dieser Standard-Currying-Funktion wird der Parameter response hinten drangehangen - man könnte ihn auch vorne anhängen (statt concat die arguments-Liste von hinten durchlaufen und alle Elemente mit unshift in args einfügen).

      In der curry-Funktion habe ich lediglich die innere return-Zeile geändert:
      return func.apply(this, slice.apply(arguments).concat(args));
      Das bewirkte schon das gezielte Ergebnis.

      Die Ajax-Funktion sieht jetzt so aus:

      function ajax(file, func_target) {  
        var tmp_ajax_obj = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');  
        tmp_ajax_obj.open('post', file);     // post => no browser-cache  
        if (func_target)  
          tmp_ajax_obj.onreadystatechange = function(){  
            if(tmp_ajax_obj.readyState == 4)  
              func_target(tmp_ajax_obj.responseText); // Aufruf der Ziel-Funktion  
          }  
        tmp_ajax_obj.send(null);  
      }  
      
      

      Damit kann ajax() sowohl mit ajax('test.txt',handler.curry(1, 2, 3)) als auch mit

      ajax('test.txt', function (resp) {  
        var a = 1, b =2, c = 3;  
        // ...  
      });
      

      aufgerufen werden.

      Gruß,
      Dodwin

      --
      Dodwin