Как я могу использовать функции обратного вызова для асинхронного XMLHttpRequest?

В настоящее время я пишу JavaScript и сбив с толку о обратном вызове. Я нашел, что это не какие-то встроенные функции, хотя... Теперь я читаю O'Relly JavaScript 5th Edition и показывает пример кода, как показано ниже:

getText = function(url, callback) // How can I use this callback?
{
    var request = new XMLHttpRequest();
    request.onreadystatechange = function()
    {
        if (request.readyState == 4 && request.status == 200)
        {
            callback(request.responseText); // Another callback here
        }
    }
    request.open('GET', url);
    request.send();
}

В принципе, я полагаю, что я не понимаю общую идею callback, хотя... Может ли кто-нибудь написать пример кода, чтобы воспользоваться callback выше?

Ответ 1

Обратные вызовы довольно просты и изящны! Из-за характера вызовов AJAX вы не блокируете выполнение вашего script до тех пор, пока ваш запрос не будет завершен (тогда он будет синхронным). Обратный вызов - это просто метод, предназначенный для обработки ответа, как только он возвращается к вашему методу.

Поскольку javascript-методы являются объектами первого класса, вы можете передавать их как переменные.

Итак, в вашем примере

getText = function(url, callback) // How can I use this callback?
{
    var request = new XMLHttpRequest();
    request.onreadystatechange = function()
    {
        if (request.readyState == 4 && request.status == 200)
        {
            callback(request.responseText); // Another callback here
        }
    }; 
    request.open('GET', url);
    request.send();
}

function mycallback(data) {
   alert(data);
}

getText('somephpfile.php', mycallback); //passing mycallback as a method

Если вы это сделаете, это означает, что вы передаете mycallback как метод, который обрабатывает ваш ответ (обратный вызов).

ИЗМЕНИТЬ

В то время как пример здесь не иллюстрирует правильную пользу обратного вызова (вы могли бы просто поместить оповещение в функцию onReadyStateChange после всех!), повторное использование, безусловно, является фактором.

Вы должны помнить, что здесь важно то, что JS-методы являются объектами первого класса. Это означает, что вы можете передавать их вокруг объектов и присоединять их ко всем видам событий. Когда события запускаются, вызываются методы, связанные с этими событиями.

Когда вы выполняете request.onreadystatechange = function(){}, вы просто назначаете этот метод для вызова при срабатывании соответствующего события.

Так здорово, что эти методы можно использовать повторно. Скажем, у вас есть метод обработки ошибок, который выдает предупреждение и заполняет некоторые поля на странице HTML в случае 404 в запросе AJAX.

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


Ответ 2

Прежде всего, я бы предложил прочитать, что такое обратный вызов. Здесь - это начало.

Общая картина

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

Одним из решений было бы делегировать работу кому-то другому и время от времени отнимать у вас обычную работу, чтобы спросить: "Это еще моя работа?". Если это так, получите результаты в некотором роде и от вас. Проблема решена.

Проблема с этим подходом заключается в том, что это не делает вашу жизнь намного проще. Теперь вы вынуждены спрашивать каждое мгновение, и вы не будете знать, что операция выполняется, как только это на самом деле (но только в следующий раз, когда вы забудете спросить). Если вы забыли спросить, вы никогда не будете уведомлены.

Лучшим решением для этого является обратный вызов: при делегировании работы предоставляйте вместе с ним функцию. Код, который фактически выполнит работу, затем promises, чтобы вызвать эту функцию, как только работа завершится. Теперь вы можете забыть все об этом и быть в безопасности, зная, что когда работа будет завершена, будет вызван ваш обратный вызов. Не раньше, и не позже.

Что такое обратный вызов здесь?

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

getText фактически выбирает использовать этот обратный вызов только тогда, когда завершается XMLHttpRequest (XHR), и в то же время он "позволяет вам знать", он также передает вам содержимое ответа HTTP (так что вы могут воздействовать на эту информацию).

Обратные вызовы и другие обратные вызовы, о мой!

Но запустите еще один момент, чтобы прочитать код. Какое значение он сохраняет в request.onreadystatechange? Какова цель request.onreadystatechange?

Ответ заключается в том, что request.onreadystatechange есть для вас, чтобы заполнить обратный вызов. Фактически, XHR дает вам способ обеспечить обратный вызов, а promises - "перезвонить вам" всякий раз, когда изменяется состояние базового HTTP-запроса.

getText - это функция, которая строит абстракцию поверх этого: она подключает свой собственный обратный вызов (анонимная функция - я называю это как "внутренняя" ) там и принимает другой обратный вызов от вас ( параметр - я буду называть его "внешним" ). Когда внутренний обратный вызов (который, помните: вызывается при каждом изменении состояния), обнаруживает, что состояние "завершено" (значение значения 4), а код состояния HTTP-ответа - 200 (что означает "ОК" ) он вызывает внешний обратный вызов, чтобы вы, пользователь getText, знали результат.

Надеюсь, у меня есть смысл.:)

Ответ 3

Мне лично я предпочитаю использовать Event Listener над обратными вызовами.

Использование Listeners удобнее, особенно если вы готовы обрабатывать сразу несколько асинхронных запросов.

Использование выглядит следующим образом (взято из https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest)

function reqListener () {
  console.log(this.responseText);
}

var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "http://www.example.org/example.txt");
oReq.send()

Ответ 4

То, что работает в правильном обратном вызове, - это определение службы, которая возвращает такое обещание!

$http.head("url2check").then(function () {
                return true;
            }, function () {
                return false;
            });

В контроллере используйте службу:

<service>.<service method>.then(function (found)) {
     if (found) {......
}

@jon правильно называть его асинхронным!

Ответ 5

Функция обратного вызова XMLHttpRequest и загрузка файлов с массивом данных

function HttpPost(url, arr, cb, form){
    if (form === undefined) { var data = new FormData(); }else{ var data = new FormData(form); }
    if (arr !== undefined) {
        for (const index in arr) {    
            data.append(index, arr[index]);
        }
    }
    var hr = new XMLHttpRequest();        
    hr.onreadystatechange=function(){
        if (hr.readyState==4 && hr.status==200){
            if( typeof cb === 'function' ){ cb(hr.responseText); }
        }
    }
    hr.upload.onprogress = function(e) {
        var done = e.position || e.loaded, total = e.totalSize || e.total;
        console.log('xhr.upload progress: ' + done + ' / ' + total + ' = ' + (Math.floor(done/total*1000)/10) + '%');
    };
    hr.open("POST",url,true);
    hr.send(data);
}

// HttpPost callback
function cb_list(res){
    console.log(res);
    var json = JSON.parse(res);
    console.log(json.id + ' ' + json.list);
    // loop
    for (var objindex in json.list){
        console.log(json.list[objindex].id);
    }
}

Пример:

var data = [];
data["cmd"] = "get-cos";
var form = $('#form')[0];

HttpPost('/api-load', data, cb_list, form);

<form id="form" method="POST" enctype="multipart/form-data">
    <input type="file" name="file[]" multiple accept="image/*">
</form>

Содержание заголовка Http

hr.setRequestHeader("Content-Type", "application/json"); 
// data:
var json = {"email": "[email protected]", "password": "101010"}
var data = JSON.stringify(json);

hr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// data: 
var data = "fname=Henry&lname=Ford";