Jak struktura JavaScript callback tak, że zakres funkcji jest utrzymywany prawidłowo

Używam XMLHttpRequest i chcę uzyskać dostęp do zmiennej lokalnej w funkcji wywołania zwrotnego success.

Oto kod:

function getFileContents(filePath, callbackFn) {  
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            callbackFn(xhr.responseText);
        }
    }
    xhr.open("GET", chrome.extension.getURL(filePath), true);
    xhr.send();
}

I chcę to tak nazywać:

var test = "lol";

getFileContents("hello.js", function(data) {
    alert(test);
});

Tutaj, test byłoby poza zakresem funkcji zwrotnej, ponieważ tylko zmienne funkcji zamykającej są dostępne wewnątrz funkcji zwrotnej. Jaki jest najlepszy sposób, aby przekazać test do funkcji callback, aby alert(test); wyświetlał test poprawnie?

Edytuj:

Teraz, jeśli mam następujący kod wywołujący funkcję zdefiniowaną powyżej:

for (var test in testers) {
    getFileContents("hello.js", function(data) {
        alert(test);
    });
}

Kod alert(test); wyświetla tylko ostatnią wartość test z pętli for. Jak to zrobić, aby wydrukowała wartość test w czasie, w którym funkcja getFileContents została wywołana? (Chciałbym to zrobić bez zmiany getFileContents, ponieważ jest to bardzo ogólna funkcja pomocnicza i nie chcę, aby była specyficzna, przekazując do niej określoną zmienną, taką jak test.

Author: Chetan, 2010-05-25

4 answers

Z kodem, który podałeś test nadal będzie w zasięgu wewnątrz wywołania zwrotnego. xhr nie będzie, poza xhr.responseText przekazaniem jako data.

Aktualizacja z komentarza :

Zakładając, że Twój kod wygląda mniej więcej tak:

for (var test in testers)
  getFileContents("hello"+test+".js", function(data) {
    alert(test);
  });
}

W trakcie działania skryptu test zostaną przypisane wartości kluczy w testers - getFileContents jest wywoływany za każdym razem, co uruchamia żądanie w tle. Po zakończeniu żądania wywołuje callback. test będzie zawiera wartość końcową z pętli, ponieważ pętla ta została już wykonana.

Istnieje technika, której możesz użyć, zwana zamknięciem, która naprawi tego rodzaju problem. Możesz utworzyć funkcję, która zwraca funkcję zwrotną, tworząc nowy zakres, w którym możesz trzymać swoje zmienne za pomocą:

for (var test in testers) {
  getFileContents("hello"+test+".js", 
    (function(test) { // lets create a function who has a single argument "test"
      // inside this function test will refer to the functions argument
      return function(data) {
        // test still refers to the closure functions argument
        alert(test);
      };
    })(test) // immediately call the closure with the current value of test
  );
}

To w zasadzie stworzy nowy zakres (wraz z naszą nową funkcją), który "utrzyma" wartość test.

Inny sposób pisania to samo:

for (var test in testers) {
  (function(test) { // lets create a function who has a single argument "test"
    // inside this function test will refer to the functions argument
    // not the var test from the loop above
    getFileContents("hello"+test+".js", function(data) {
        // test still refers to the closure functions argument
        alert(test);
    });
  })(test); // immediately call the closure with the value of `test` from `testers`
}
 39
Author: gnarf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-25 10:00:20

JavaScript używa zakresów leksykalnych , co zasadniczo oznacza, że twój drugi przykład kodu będzie działał tak,jak zamierzasz.

Rozważ poniższy przykład, zapożyczony z przewodnika Davida Flanagana Definitive Guide1:

var x = "global";

function f() {
  var x = "local";
  function g() { alert(x); }
  g();
}

f();  // Calling this function displays "local"

Należy również pamiętać, że w przeciwieństwie do C, C++ i Javy, JavaScript nie ma zakresu na poziomie bloków.

Ponadto możesz być również zainteresowany zapoznaniem się z poniższym artykułem, który bardzo polecam:


1 David Flanagan: JavaScript-The Definitive Guide , Wydanie Czwarte, Strona 48.

 7
Author: Daniel Vassallo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:51

W tym scenariuszu test zostanie rozwiązany zgodnie z oczekiwaniami, ale wartość this może być inna. Zwykle, aby zachować zakres, Należy uczynić go parametrem funkcji asynchronicznej w następujący sposób:

function getFileContents(filePath, callbackFn, scope) {  
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            callbackFn.call(scope, xhr.responseText);
        }
    }
    xhr.open("GET", chrome.extension.getURL(filePath), true);
    xhr.send();
}


//then to call it:
var test = "lol";

getFileContents("hello.js", function(data) {
    alert(test);
}, this);
 2
Author: Igor Zevaka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-24 22:54:39

Miałem podobny problem. Mój kod wyglądał tak:

for (var i=0; i<textFilesObj.length; i++)
{
    var xmlHttp=new XMLHttpRequest();
    var name = "compile/" + textFilesObj[i].fileName;
    var content = textFilesObj[i].content;

    xmlHttp.onreadystatechange=function()
    {
        if(xmlHttp.readyState==4)
        {
            var responseText = xmlHttp.responseText;
            Debug(responseText);
        }
    }

    xmlHttp.open("POST","save1.php",true);
    xmlHttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
    xmlHttp.send("filename="+name+"&text="+encodeURIComponent(content));
}

Wyjście było często tekstem odpowiedzi ostatniego obiektu (ale nie zawsze). W rzeczywistości był to rodzaj przypadkowy i nawet liczba odpowiedzi była różna (dla stałego wejścia). Okazuje się, że funkcja powinna być zapisana:

    xmlHttp.onreadystatechange=function()
    {
        if(this.readyState==4)
        {
            var responseText = this.responseText;
            Debug(responseText);
        }
    }

Zasadniczo w pierwszej wersji obiekt "xmlHttp" zostanie ustawiony na wersję w bieżącym etapie pętli. Większość czasu pętla skończyłaby się przed którymkolwiek z żądania AJAX zostały zakończone, więc w tym przypadku "xmlHttp" odnosił się do ostatniej instancji pętli. Jeśli to ostateczne żądanie zakończy się przed innymi żądaniami, wszystkie one wydrukują odpowiedź z ostatniego żądania za każdym razem, gdy zmieni się ich stan gotowości (nawet jeśli ich stan gotowości wynosił

Rozwiązaniem jest zastąpienie "XMLHTTP "przez " this", aby funkcja wewnętrzna odnosiła się do prawidłowej instancji obiektu request za każdym razem, gdy wywołane jest wywołanie zwrotne.

 0
Author: Bruce,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-27 00:07:21