Подтвердить что ты не робот

Как структурировать обратный вызов javascript, чтобы область функций поддерживалась должным образом

Я использую XMLHttpRequest, и я хочу получить доступ к локальной переменной в функции обратного вызова успеха.

Вот код:

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();
}

И я хочу назвать это следующим образом:

var test = "lol";

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

Здесь test выходит за рамки функции обратного вызова, так как внутри функции обратного вызова доступны только входящие функциональные переменные. Каков наилучший способ передать test функции обратного вызова, чтобы alert(test); отображал test правильно?

Изменить:

Теперь, если у меня есть следующий код, вызывающий функцию, определенную выше:

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

Код alert(test); выводит только последнее значение test из цикла for. Как сделать так, чтобы он печатал значение test в течение времени, когда вызывалась функция getFileContents? (Я хотел бы сделать это, не меняя getFileContents, потому что это очень общая вспомогательная функция, и я не хочу делать ее конкретным, передавая ей определенную переменную типа test.

4b9b3361

Ответ 1

С кодом, который вы предоставили test, по-прежнему будет отображаться внутри области обратного вызова. xhr не будет, кроме xhr.responseText, передается как data.

Обновлено из комментария:

Предполагая, что ваш код выглядит примерно так:

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

Как только этот script запускается, test будет назначаться, значения ключей в testers - getFileContents вызывается каждый раз, который запускает запрос в фоновом режиме. По завершении запроса он вызывает обратный вызов. test будет содержать FINAL VALUE из цикла, поскольку этот цикл уже завершил выполнение.

Существует метод, который вы можете использовать, называемый замыканием, который будет исправлять эту проблему. Вы можете создать функцию, которая возвращает вашу функцию обратного вызова, создавая новую область, которую вы можете удерживать на своих переменных:

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
  );
}

В основном это создаст новую область (наряду с нашей новой функцией), которая будет "удерживать" значение test.

Другой способ написания такого же типа:

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`
}

Ответ 2

JavaScript использует лексическое охват, что в основном означает, что ваш второй пример кода будет работать так же, как и то, как вы собираетесь его работать.

Рассмотрим следующий пример, взятый у Дэвида Фланагана Окончательное руководство 1:

var x = "global";

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

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

Также имейте в виду, что в отличие от C, С++ и Java, JavaScript не имеет области уровня блока.

Кроме того, вы также можете быть заинтересованы в проверке следующей статьи, которую я настоятельно рекомендую:


1 Дэвид Фланаган: JavaScript - окончательное руководство, четвертое издание, стр. 48.

Ответ 3

В этом случае тест будет разрешен так, как вы ожидали, но значение this может отличаться. Обычно, чтобы сохранить область действия, вы должны сделать ее параметром для асинхронной функции, например:

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);

Ответ 4

У меня возникла аналогичная проблема. Мой код выглядел так:

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));
}

Выходом часто являлся текст ответа последнего объекта (но не всегда). На самом деле это было случайным и даже количество ответов варьировалось (для постоянного ввода). Оказывается, что функция должна была быть написана:

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

В принципе, в первой версии объект "xmlHttp" устанавливается на версию на текущем этапе цикла. Большую часть времени цикл завершил бы до завершения любого из запросов AJAX, поэтому в этом случае "xmlHttp" ссылается на последний экземпляр цикла. Если этот окончательный запрос закончен перед другими запросами, все они будут печатать ответ от последнего запроса, когда их состояние готовности изменится (даже если их состояния готовности были < 4).

Решение заключается в замене "xmlHttp" на "this", поэтому внутренняя функция ссылается на правильный экземпляр объекта запроса каждый раз, когда вызывается обратный вызов.