Полное руководство по XMLHttpRequest

Хватит искать и прочитай это

Прежде всего, давайте разберемся, о чем мы будем говорить.

Что это

Изобретен Microsoft в начале 90-х годов и сокращенно называется XHR, XMLHttpRequest.  – это набор API-интерфейсов, которые могут использоваться языками сценариев веб-браузеров, такими как JavaScript, для передачи данных в и из Веб-сервер, использующий HTTP.

XHR можно использовать с протоколами, отличными от HTTP, и данные могут быть в форме не только XML, но и JSON, HTML или >простой текст.

Что вы можете сделать с XHR

Все современные браузеры имеют встроенный объект XMLHttpRequest для запроса данных с сервера. С объектом XHR вы можете:

  • Обновить веб-страницу без перезагрузки страницы
  • Запросить данные с сервера — после загрузки страницы.
  • Получить данные с сервера — после загрузки страницы
  • Отправить данные на сервер — в фоновом режиме

Что мы собираемся делать

Мы собираемся использовать поддельный онлайн REST API для тестирования и создания прототипов, например reqres (еще один хороший — JSONPlaceholder), он отлично подходит для учебных пособий, тестирования и примеров кода. В этом посте вы узнаете:

  • как получать данные с помощью запросов GET и как отправлять с помощью POST
  • Что такое callback и что такое promise (кратко)
  • Улучшить наш код, чтобы сделать его более универсальным
  • Обработка ошибок запроса
  • Визуализируйте полученные данные

Получение данных

Нам нужно создать новый объект xhr, создав экземпляр XMLHttpRequest:

const xhr = new XMLHttpRequest();

это встроенная функция конструктора в вашем браузере, поэтому вам не нужно добавлять какую-либо специальную библиотеку или пакет.

На следующем шаге нам нужно подготовить HttpRequest к отправке, используя функцию, которая принимает два аргумента:
1) используемый HTTP-метод
2) URL-адрес, на который вы хотите отправить отправить этот запрос

xhr.open('GET', 'https:reqres.in/api/users')

Теперь мы можем отправить предварительно настроенный запрос:

xhr.send();

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

Небольшая преамбула
Есть два основных способа прослушивания события onload в нашем запросе xhr, а именно:

/*1*/ xhr.addEventListener("load", reqListener);

/*2*/ xhr.onload = () => { };

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

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

xhr.onload = () => { console.log(xhr.response); };

в этом случае увидит в журнале консоли кучу данных, напечатанных в виде строки, но на самом деле это ответ JSON. Чтобы преобразовать этот ответ в объект Javascript, чтобы манипулировать им, мы можем его проанализировать:

xhr.onload = () => {
    const data = JSON.parse(xhr.response);
    console.log(data);
};

Другой способ проанализировать ответ JSON — установить для атрибута responseType значение «json»:

xhr.responseType = 'json';
xhr.onload = () => {
    const data = xhr.response;
    console.log(data);
};

Наш код должен выглядеть так:

const xhr = new XMLHttpRequest();
xhr.open('GET','https://reqres.in/api/users');
xhr.responseType = 'json';
xhr.onload = () => {
    const data = xhr.response;
    console.log(data);
};
xhr.send();

Отправка данных

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

Единственное, что изменится, — это вызов xhr.open(), так как нам нужно изменить метод запроса на POST, установить URL-адрес для запроса и добавить данные для пользователя, которого мы хотим создать:

xhr.open('POST','https://reqres.in/api/users',{
    "name":"morpheus",
    "job":"leader"
});

reqres.in ответит на наш запрос со статусом 2xx и отправит нам дополнительную информацию об успешном создании пользователя с таким объектом ответа:

createdAt: "2020-02-03T17:10:31.110Z"
​id: "644"

Что такое обратный вызов

Проще говоря: обратный вызов — это функция, которая должна быть выполнена после завершения выполнения другой функции — отсюда и название «обратный вызов».

Брэндон Морелли в этой прекрасной статье об обратных вызовах.

Что такое обещание

(Промисы изначально работают только в современных браузерах)
XHR на основе промисов может помочь вам избежать глубоко вложенных обратных вызовов и разрешить цепочку методов с использованием .then()
Вот как может выглядеть традиционная модель обратного вызова:

first(a,function(b){
    second(b,function(c){
        third(c,function(d){
            fourth(d);
        });
    });
});

И как это может выглядеть при использовании подхода, основанного на промисах.

first(a)
    .then(function(b){
        return second(b);
    })
    .then(function(c){
        return third(c);
    })
    .then(function(d){
        return fourth(d);
    })
    .catch(function (error){
        // error at any point of the chain
    });

довольно яснее, а?

Давайте улучшим код

Хорошо, теперь, когда мы поняли основы, пришло время сделать вещи более профессиональными, создав функцию для получения/отправки данных через HttpRequests:

const sendHttpRequest = (method, url) => {
    const xhr = new XMLHttpRequest();
    xhr.open(method,url);
    xhr.responseType = 'json';
    xhr.onload = () => {
        const data = xhr.response;
        console.log(data);
    };
    xhr.send();
};

Еще не там. Давайте добавим обещания.

const sendHttpRequest = (method, url) => {
    //Adding promise
    const promise = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(method,url);
        xhr.responseType = 'json';
        xhr.onload = () => {
            resolve(xhr.response);
        };
        xhr.send();
    });
    //returning the promise    
    return promise
};
//Using .then() to retrieve data
const getData = () => {
    sendHttpRequest('GET','https://reqres.in/api/users')
    .then(responseData => {
        console.log(responseData);
    });
};

Мы также можем отправлять POST-запросы, но необходимо внести некоторые изменения в sendHttpRequest:

//adding data parameter
const sendHttpRequest = (method, url, data) => {
    const promise = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(method,url);
        xhr.responseType = 'json';
        //signals that we are appending json data in POST
        if (data){
            xhr.setRequestHeader('Content-Type','application/json');
        }        
        xhr.onload = () => {
            resolve(xhr.response);
        };
        //appending json data
        xhr.send(JSON.stringify(data));
    });
    return promise
};
//Sending POST request
const sendData = () => {
    sendHttpRequest('POST','https://reqres.in/api/users',{
        name: "morpheus",
        job: "leader"
    }).then(responseData => {
        console.log(responseData);
    })
};

Отлично, теперь мы можем вызывать getData() и sendData(), чтобы протестировать их:

Обработка ошибок

Все идет нормально. Но как насчет ошибок?
Прежде всего, прежде чем обрабатывать ошибки xhr, нам нужно понять небольшую разницу: есть два основных типа ошибок:

  • Ошибки ответа
    Не удалось ответить из-за сетевого подключения или неверного URL-адреса.
  • Ошибки состояния
    На самом деле мы можем получить ответ от сервера, но со статусом ошибки (например, 400); это потому, что сервер не распознал наш запрос (например, мы пропустили параметр в запросе POST).

Давайте изменим наш код для обработки этих ошибок:

const sendHttpRequest = (method, url, data) => {
    const promise = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(method,url);
        xhr.responseType = 'json';
        if (data){
            xhr.setRequestHeader('Content-Type','application/json');
        }        
        xhr.onload = () => {
            //rejecting promise on status error
            if(xhr.status >= 400) {
                reject(xhr.response);
            } else {
                resolve(xhr.response);
            }
        };
        //rejecting promise on response errors
        xhr.onerror = () => {
            reject('Something went wrong!');
        };       
        xhr.send(JSON.stringify(data));
    });
    return promise
};
const sendData = () => {
    sendHttpRequest('POST','https://reqres.in/api/users',{
        name: "morpheus",
        job: "leader"
    }).then(responseData => {
        console.log(responseData);
    //catching and printing errors   
    }).catch(err => {
         console.log(err);
    });
};

Теперь, если мы отключим интернет-соединение и попытаемся вызвать sendData(), мы получим в журнале something went wrong!, то же самое, если мы попытаемся достичь недопустимого URL-адреса.

В зависимости от запроса POST, если мы попытаемся опустить поле в отправляемых данных:

const sendData = () => {
    sendHttpRequest('POST', 'https://reqres.in/api/register', {
        email: '[email protected]'
        // password: 'pistol'
    }).then(responseData => {
        console.log(responseData);
    }).catch(err => {
        console.log(err);
    });
};

мы получим ошибку статуса error: "Missing email or username"

Визуализируйте полученные данные

Вы можете просто визуализировать данные, используя функцию, которая добавляет элементы DOM.

// Visualize in HTML the data from GET requests
    const visualize = (data) => {
      // Referring to an existing element in HTML code
      // (in this case <div id="data-from-xhr"></div>)
      let div = document.getElementById('data-from-xhr')
      // Clear div element
      div.innerHTML = ''
// For every data element:
      data.map(userData => {
        // Create DOM elements
        let user = document.createElement('div')
        let avatar = document.createElement('img')
        let email = document.createElement('p')
        let fullName = document.createElement('p')
// Fill DOM elements with passed data
        avatar.src=userData.avatar
        email.innerText = userData.email
        fullName.innerText = userData.first_name + userData.last_name
// Create a user node each one with avatar, email and fullName
        user.appendChild(avatar)
        user.appendChild(email)
        user.appendChild(fullName)
// Append the user node to div in HTML
        div.appendChild(user)
     })
};

с некоторыми css это должно выглядеть так:

Немного конфет для вас

Состояния запроса:
мы можем отслеживать состояние запроса, используя событие onreadystatechange:

xhr.onreadystatechange = function () {
    if(xhr.readyState == 1) {
        console.log('Request started.');
    }
    
    if(xhr.readyState == 2) {
        console.log('Headers received.');
    }
    
    if (xhr.readyState == 3) {
        console.log('Data loading..!');
    }
    if (xhr.readyState == 4) {
        console.log('Request ended.');
    }
}; 

Прервать запрос
прервать запрос XHR в любое время, вызвав метод abort():

xhr.abort(); // cancel request

Событие выполнения
Запускается периодически во время загрузки ответов и может использоваться для отчета о ходе выполнения тяжелых сетевых запросов.

xhr.onprogress = (event) => {
    // event.loaded returns how many bytes are downloaded
    // event.total returns the total number of bytes
    // event.total only if server sends `Content-Length` header
    console.log(`Downloaded ${event.loaded} of ${event.total}`);
}

Хотите реальный пример кода?

Вот мой репозиторий на github о XHR.

использованная литература

youtube.com: Отправка HTTP-запросов Javascript с помощью XMLHttpRequest
codeburst.io: JavaScript: что такое обратный вызов?
gomakethings.com :
XHR на основе обещаний
attacomsian.com: Выполнение HTTP-запросов с использованием XMLHttpRequest (XHR)
w3schools. com: XML HttpRequest
developer.mozilla.org: XMLHttpRequest
wikipedia.org: XMLHttpRequest