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

I want to catch all errors with the help of TRY {} CATCH(){} when I send data to a server via XMLHttpRequest.

How can I receive all errors, such as net::ERR_INTERNET_DISCONNECTED, etc. ?

Paul's user avatar

Paul

26.2k12 gold badges85 silver badges119 bronze badges

asked Nov 5, 2014 at 12:02

Hit-or-miss's user avatar

2

Try catches didn’t work for me. I personally ended up testing for response == «» and status == 0.

        var req = new XMLHttpRequest();
        req.open("post", VALIDATE_URL, true);
        req.onreadystatechange = function receiveResponse() {

            if (this.readyState == 4) {
                if (this.status == 200) {
                    console.log("We got a response : " + this.response);
                } else if (!isValid(this.response) && this.status == 0) {
                    console.log("The computer appears to be offline.");
                }
            }
        };
        req.send(payload);
        req = null;

answered Apr 30, 2015 at 22:03

Francois Dermu's user avatar

Francois DermuFrancois Dermu

4,4472 gold badges22 silver badges14 bronze badges

4

You should put all the statements which you think that will cause an exception in a try block. After that you can give several catch statements — each one for one exception. In last you can give finally as well — this statement will be executed after Try block regardless of whether or not exception was thrown or caught.

Syntax can be like this:

try{
try_statements
}

[catch (exception_var_2) { catch_statements_1 }]
[catch (exception_var_2) { catch_statements_2 }]
...
[catch (exception_var_2) { catch_statements_N }]

[finally { finally_statements }]

Example:

try {
   myroutine(); // may throw three exceptions
} catch (e if e instanceof TypeError) {
   // statements to handle TypeError exceptions
} catch (e if e instanceof RangeError) {
   // statements to handle RangeError exceptions
} catch (e if e instanceof EvalError) {
   // statements to handle EvalError exceptions
} catch (e) {
   // statements to handle any unspecified exceptions
   logMyErrors(e); // pass exception object to error handler
}

You can read more here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try…catch

answered Nov 5, 2014 at 12:11

disp_name's user avatar

disp_namedisp_name

1,4482 gold badges20 silver badges47 bronze badges

1

Refer this,

function createXMLHttpRequestObject()
{
  // xmlHttp will store the reference to the XMLHttpRequest object
  var xmlHttp;
  // try to instantiate the native XMLHttpRequest object
  try
  {
    // create an XMLHttpRequest object
    xmlHttp = new XMLHttpRequest();
  }
  catch(e)
  {
        try
    {
      xmlHttp = new ActiveXObject("Microsoft.XMLHttp");
    }
    catch(e) { }
  }
  // return the created object or display an error message
  if (!xmlHttp)
    alert("Error creating the XMLHttpRequest object.");
  else 
    return xmlHttp;
}

answered Nov 5, 2014 at 12:07

soundhiraraj's user avatar

2

In this lesson, we’ll complete our OpenWeather API website by incorporating error handling into our API request. What this means is having our code do something when an API call is not 200 OK, but instead any of the other possible HTTP status codes, like:

  • 400: Bad request. We’d better check the API request and make sure it’s correct.
  • 401: Unauthorized. We aren’t authorized to access the resource, which might mean we haven’t logged in correctly.
  • 403: Forbidden — we aren’t allowed to access that content.
  • 404: Not found. The resource couldn’t be found.
  • 500: Internal server error. Not our fault! Something is going on with the API. In general, if we get any 500 errors, the server is having problems.

The error handling that we’ll incorporate into our application will be basic: if the API response is not 200 OK, we’ll display an error message in the webpage letting the user know what happened, including a status code and short descriptive text. This won’t fix all of the errors that could happen, but in some cases it will give the user just enough information to fix some errors they run into, like a typo in their input.

That’s why this solution is basic — we’re making sure to communicate to the user what happened with the request. More advanced error handling might involve logging all errors that are generated by the API and using a notification system so that developers are notified of these errors and can address them. In fact, there are tech companies who sell products that do monitoring and error reporting, and most website hosting sites have such tools built-in. In this program, we’ll stick to basic error handling since learning about software monitoring and reporting tools is out of the scope of this program.

We’ll learn two ways of delivering information about errors to the user: using XMLHttpRequest object properties, and using the error messages that an API creates and sends independently.

Let’s get into it!

The Errors that We’ll Encounter


The errors that we’ll deal with — and can test for in our apps — are client errors, which are errors that come from the computer making the request to the API, also known as the «client». In this lesson, we’ll cause a few errors and see how the OpenWeather API responds:

  • A bad input
  • A bad API key
  • A bad request URL (but a good API key)

Other APIs may respond a bit differently to these errors, but using these test cases will be sufficient to ensure we have very solid, albeit basic, error handling in place.

When you add error handling to your API projects in this course section, make sure that your code handles the above three situations.

Error Handling with XMLHttpRequest.status and XMLHttpRequest.statusText


The first method of error handling is one that will stay the same with every API that you work with, so long as you are using an XMLHttpRequest object. That’s because it involves using properties from the XMLHttpRequest object: XMLHttpRequest.status and XMLHttpRequest.statusText. We briefly learned about these properties in a previous lesson, so let’s do another review here:

  • status: The status is the HTTP status code of the API response. A 200 means it was successful. As we just reviewed, there are many other codes such as 404 not found and so on.
  • statusText: This is the short description or title that accompanies the status code. For a 200 status, the statusText will be «OK». For a 404 status, the statusText will be «Not Found».

You should not bother to memorize all status codes, and instead get in the habit of looking them up anytime you run into a new one. You can find a complete list of status codes on Wikipedia and MDN.

The great thing about these two properties is that they are automatically populated by the XMLHttpRequest object when we make an API call. That means we can always rely on them to contain relevant information to the API call we just made. Let’s go ahead and use these two properties in our code.

Using XMLHttpRequest.status and XMLHttpRequest.statusText

The first section of code that we’ll want to update is our "loadend" event listener. Currently our code looks like this:

function getWeather(city) {
  ...

  request.addEventListener("loadend", function() {
    const response = JSON.parse(this.responseText);
    if (this.status === 200) {
      printElements(response, city);
    }
  });

  ...
}

We’ll add an else statement to our branching to handle every other case — that is, when this.status does NOT equal 200.

Here’s our new code:

src/index.js

function getWeather(city) {
  ...

  request.addEventListener("loadend", function() {
    const response = JSON.parse(this.responseText);
    if (this.status === 200) {
      printElements(response, city);
    } else {
      printError(this, city);
    }
  });

  ...
}

Take note that the printError function doesn’t exist yet. However, we know what direction we’re heading in, so we can already define the name of the function and what arguments should be passed into it. Let’s break this down:

  • We call our function printError, because it will handle printing error messages to the DOM.
  • We’re passing in two arguments: this and city.
    • Why this? Remember that this represents the XMLHttpRequest object and we need to access the status and statusText properties inside of it.
    • Why city? Remember that the city variable represents the user’s input. We don’t actually have to include this information when we print an error message to the DOM, but it will make our error message more descriptive.

With this new code, we’re handling all other status codes at once, in one else statement. While we could target specific status codes, something like this:

  request.addEventListener("loadend", function() {
    const response = JSON.parse(this.responseText);
    if (this.status === 200) {
      printElements(response, city);
    } else if (this.status === 404) {
      printError(this, city);
    } else if (this.status === 401) {
      printError(this, city);
    }
    ... 
    ...
  });

This is verbose and unnecessary. We can capture all non-200 status codes with just one else statement.

Alright, let’s create our printError function next. We can add this to our user interface logic, just above the printElements function.

src/index.js

// UI Logic

function printError(request, city) {
  document.querySelector('#showResponse').innerText = `There was an error accessing the weather data for ${city}:  ${request.status} ${request.statusText}`;
}

We start out by declaring two descriptive parameters that match the arguments we already passed in: request and city.

Next, we access the P tag with the id of showResponse and update its inner text with an error message. Here, too, we use template literals to add variables directly to our string.

Since the request parameter represents the XMLHttpRequest object, we can access the status and statusText properties with dot notation: request.status and request.statusText.

The extra touch with this error message is including the city. You can come up with your own error message in your own projects, and include as much extra detail as you want.

Testing the Error Handling

Now that we have our code in place, let’s test it out to make sure that it functions as expected. We have three test cases that we’ll try out:

  • A bad input
  • A bad API key
  • A bad request URL (but a good API key)

Let’s start with a bad user input. This is asking the question, what will the status code be if a resource cannot be found given the user’s input?

Start your application, and enter in a phony input like «wefsdvx». We should see our error message print on the page!

There was an error accessing the weather data for wefsdvx: 404 Not Found

The error message "There was an error accessing the weather data for wefsdvx: 404 Not Found" is successfully printed to the webpage.

Very cool! We can see our error handling working as expected.

Next, let’s try a bad API key. With this test, we’re asking the question, what will the status code be if we’re not authorized to access a resource on an API?

With this one, we’ll need to close our server, change the API key in the .env, and then restart the server. We can mess up our API key simply by removing the last character. Go ahead and do that now, and then restart the server.

If we enter a location, even a bogus one, we should see a new status code printed to the webpage.

There was an error accessing the weather data for Portland, Oregon: 401 Unauthorized

The error message "There was an error accessing the weather data for Portland, Oregon: 401 Unauthorized" is successfully printed to the webpage.

Alright, let’s move onto our last test. Before we do, be sure to close your server, fix your API key, and restart it.

With this next test, we’re asking the question, what will the status code be if our request is written incorrectly?

To cause this error, let’s change something about our request URL; we’ll change the request parameter q=${city} to be p=${city}:

function getWeather() {
  ...
  const url = `http://api.openweathermap.org/data/2.5/weather?p=${city}&appid=${process.env.API_KEY}`;
  ...
}

Enter in a location to see what sort of error message this typo in our parameter causes:

There was an error accessing the weather data for portland: 400 Bad Request

The error message "There was an error accessing the weather data for portland: 400 Bad Request" is successfully printed to the webpage.

Sure enough, we’ll get an error message about our request being incorrect in some way.

Since we as developers are on the hook for ensuring that our request URLs work as expected, this last situation should only come up in development. However, trying out many different ways to break our code is always a good learning process, and it helps us ensure that our error handling works as expected.

Using an API’s Error Messages


Next, let’s look at the other way we can handle errors when we make a request to an API: using the API’s own error messages. The important gotcha about this method is that no two APIs are the same, which means how errors and relevant info is reported will always vary. Sometimes APIs have extensive internal error messages, and other APIs have none at all. We’ll revisit this in just a moment.

One thing to note about API call errors is that the status code will never change for the type of error that’s reported whether you are using the API’s custom error messages or XMLHttpRequest.status and XMLHttpRequest.statusText.

So why bother using the API’s custom error messages if we can essentially get the same information via the XMLHttpRequest object? Well, custom error messages that are crafted by an API are usually more descriptive, which means we can use them to provide a better user and developer experience. Let’s look at the same 3 test cases that we just tried out and see what the API returns as far as an error message.

  1. A bad input
    • XMLHttpRequest object returns 404 Not Found
    • The API returns 404 «city not found»
  2. A bad API key
    • XMLHttpRequest object returns 401 Unauthorized
    • The API returns 401 «Invalid API key. Please see http://openweathermap.org/faq#error401 for more info.»
  3. A bad request URL (but a good API key)
    • XMLHttpRequest object returns 400 Bad Request
    • The API returns 400 «Nothing to geocode»

As we can see, the OpenWeather API returns error messages that are way more descriptive, which can be a big benefit to users and developers alike.

So, let’s learn how to access these errors.

Figuring Out How an API Reports Errors

Keep in mind that every API will report errors differently. To find out how an API reports errors, we need to do two things:

  • Cause errors in our API calls and see what gets returned.
  • Look for information in the documentation about how errors are structured.

The order in which you do the two tasks above doesn’t matter. What matters is that you do both. We’ll start with the first one — introducing errors into our API calls and seeing what gets returned. We can easily do this with Postman!

Introducing Errors in an API Call via Postman

We’ll look at one example to see how the OpenWeather API structures its errors. We’ll look at a bad input (status code 404). To see what a bad input returns we can enter the following URL into Postman:

http://api.openweathermap.org/data/2.5/weather?q=a3edf3&appid=[YOUR-API-KEY-HERE]

Where [YOUR-API-KEY-HERE] is replaced with your API key.

After we send this request, we should get a response object from the API that looks like this:

{
    "cod": "404",
    "message": "city not found"
}

What this tells us is that when there’s an error, the OpenWeather API returns two keys: "cod" and "message". The "cod" key contains the HTTP status code, and the "message" key contains the description for what went wrong.

If we then try out the two remaining errors, one for a bad API key and one for a bad request URL, we’ll find that this same structure is returned to us, where "cod" contains the HTTP status code and "message" contains the description of the error.

With this information already we can update our JS to access and print this information, but before we do that, we should always do a little research on the API documentation. Why? There just may be more information on how errors are reported.

Reviewing the API Documentation on Errors

When you are ready to look through the API’s documentation to learn how it reports errors, there’s a couple places you should look:

  1. If the API you are using has multiple APIs, start in the documentation for the specific API you are using. For the OpenWeather API, we’re getting current weather data, so I would start by looking in the current weather documentation. Look at any table of contents, and try using ctrl + f to search for «error» to see if you can quickly locate the section that goes over error reporting.
  2. If you can’t find any information about error reporting for a specific API, go one level up. For the OpenWeather API, this would be looking at their webpage that lists all APIs.
  3. If you still can’t find any information about error reporting, I would look for a FAQ page, or something similar. Some APIs may include a search option to search their API documentation, which you could also try using.
  4. If you still can’t find any information about error reporting, you’ll need to assume the API doesn’t have custom error messages when a response is not 200 OK.

If you found in your testing in Postman that the API does in fact return custom error messages, you can choose to incorporate what you’ve learned from the testing the API or just stick with the XMLHttpRequest object’s status and statusText properties to handle reporting errors.

As we noted earlier, each API is different, so the process of locating its documentation on error reporting (if any) and learning how to use it will vary. For the OpenWeather API, it turns out that it has a section dedicated to API errors at the bottom of its FAQ page, which was hard for me to find. What’s more, it doesn’t actually show what the API response object for each error looks like.

So, do your best to research the API documentation to find helpful information on errors, and then always try introducing errors into your request via Postman to test how the API responds. When in doubt, stick to using XMLHttpRequest.status and XMLHttpRequest.statusText to deliver error messages to the user.

Displaying Custom Error Messages from an API

Since the OpenWeather API does include custom error messages, let’s briefly look at how we can access that information in our project. We’ll update our existing printError function to also print the API’s error data along with the information from the XMLHttpRequest object.

First, we’ll need to update our "loadend" event listener in the getWeather function.

  request.addEventListener("loadend", function() {
    const response = JSON.parse(this.responseText);
    console.log(response);
    if (this.status === 200) {
      printElements(response, city);
    } else {
      // there's a new argument
      printError(this, response, city);
    }
  });

Notice that we’ve added a new argument to the printError function: response. Now our printError function will have access to the API’s response (response), as well as the XMLHttpRequest object (represented by this) and the user input (city).

Keep in mind that the API will return different information based on whether the API call was successful or not. If we had an error free request with a status of 200 OK, the response variable will represent the weather data:

{
    "coord": {
        "lon": -122.6762,
        "lat": 45.5234
    },
    "weather": [
        {
            "id": 800,
            "main": "Clear",
            "description": "clear sky",
            "icon": "01d"
        }
    ],
    "base": "stations",
    "main": {
        "temp": 305.23,
        "feels_like": 306.11,
        "temp_min": 303.01,
        "temp_max": 308.14,
        "pressure": 1012,
        "humidity": 43
    },
    "visibility": 10000,
    "wind": {
        "speed": 2.68,
        "deg": 225,
        "gust": 2.68
    },
    "clouds": {
        "all": 6
    },
    "dt": 1658779129,
    "sys": {
        "type": 2,
        "id": 2008548,
        "country": "US",
        "sunrise": 1658753199,
        "sunset": 1658807250
    },
    "timezone": -25200,
    "id": 5746545,
    "name": "Portland",
    "cod": 200
}

If there was an error and the status code is anything other than 200 OK, response will be set to an error object that looks something like this:

{
    "cod": "404",
    "message": "city not found"
}

Now it’s time to update the printError function to have a new parameter apiResponse, and to do something with it:

function printError(request, apiResponse, city) {
  document.querySelector('#showResponse').innerText = `There was an error accessing the weather data for ${city}: ${request.status} ${request.statusText}: ${apiResponse.message}`;
}

Since the apiResponse parameter represents the response object from the API, we can access it with object property accessors: apiResponse.message.

Now our error messages will print look like this:

There was an error accessing the weather data for fake city: 404 Not Found: city not found

The error message "There was an error accessing the weather data for fake city: 404 Not Found: city not found"" is successfully printed to the webpage.

Of course, you don’t have to stick with this formatting for the error message — as long as you inform the user why there was an error, including the HTTP status code and description, that’s sufficient for basic error handling.

Summary


In this lesson we learned how to add basic error handling to our API calls. The goal of basic error handling is to inform the user why an API call went wrong, which includes relaying two key pieces of information: the HTTP status code and the title/description of that status code.

We learned two ways to communicate error messages to the user:

  1. By printing the XMLHttpRequest.status and XMLHttpRequest.statusText to the webpage.
  2. By learning how the API structures custom error messages (if it has any) and printing those to the webpage.

Not all APIs have custom error messages, which is why it’s important to test out error responses with your API and review its documentation. When APIs do include custom error messages, they are usually more descriptive than reporting the HTTP status code and title from the XMLHttpRequest object, which is why it’s good to research and use in your code.

To test out your error handling, we recommend these three test cases:

  • Using a bad input, like a non-existent city, which causes a «404 Not Found» error.
  • Using a bad API key, which causes a «401 Not Authorized» error.
  • Using a bad request URL (but a good API key), like messing up the request parameters, which causes a «400 Bad Request» error.

Including basic error handling for API calls is required on this section’s independent project, so make sure to practice it during this course section.

Later on in this course section, we’ll continue to learn how to handle errors with the new tools we learn to use to handle asynchrony and making API calls. Up next, we’ll learn about the DevTools Network tab, which we can use to inspect our API calls and test how our error handling deals with one more type of error: a network error.


Example GitHub Repo for API Project

The above link takes you to a branch within a repo. Make sure that you are referencing the code from the branch called 1_xhr_api_call. This is the default branch, so running git clone... with the URL of the repo home page will automatically clone down the branch called 1_xhr_api_call. As needed, review the lesson on accessing code from different branches.

Время на прочтение
4 мин

Количество просмотров 11K

Если вы пришли сюда только ради ответа и вам не интересны рассуждения — листайте вниз :)

Как все начиналось

Для начала, давайте вспомним, а как вообще ловят ошибки в js, будь то браузер или сервер. В js есть конструкция try...catch.

try {
    let data = JSON.parse('...');
} catch(err: any) {
		// если произойдет ошибка, то мы окажемся здесь
}

Это общепринятая конструкция и в большинстве языков она есть. Однако, тут есть проблема (и как окажется дальше — не единственная), эта конструкция «не будет работать» для асинхронного кода, для кода который был лет 5 назад. В те времена, в браузере использовали для Ajax запроса XMLHttpRequest.

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com', true);
xhr.addEventListener('error', (e: ProgressEvent<XMLHttpRequestEventTarget>) => {
    // если произойдет ошибка, то мы окажемся здесь
});

Тут используется механизм подписки на событие возникновения ошибки. В данном случае, переменная e является событием, фактически мы ушли от настоящей ошибки и закрылись некоторой абстракцией, за которой спрятана настоящая ошибка, доступа к которой у нас нет.

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

import fs from 'fs';

fs.readFile('file.txt', (err, data) => {
    if (err) {
        // обработка ошибки
    }
    // если все хорошо, работаем с данными
});

Если мы посмотрим какой тип имеет переменная err, то увидим следующее:

interface ErrnoException extends Error {
    errno?: number | undefined;
    code?: string | undefined;
    path?: string | undefined;
    syscall?: string | undefined;
}

Тут действительно находится ошибка. По сути, это тот же способ, что и выше, только в этом случает мы получаем объект Error.

Через некоторое время, в Javascript появились Promise. Они, безусловно, изменили разработку на js к лучшему. Ведь никто* никто не любит городить огромные конструкции из функций обратного вызова.

fetch('https://api.example.com')
  .then(res => {
    // если все хорошо, работаем с данными
  })
  .catch(err => {
		// обработка ошибки
  });

Несмотря на то, что внешне этот пример сильно отличается от первого, тем не менее, мы видим явную логическую связь. Очевидно, что разработчики хотели сделать похожую на try...catch конструкцию. Со временем, появился еще один способ обработать ошибку в асинхронном коде. Этот способ, по сути, является лишь синтаксическим сахаром для предыдущего примера.

try {
  const res = await fetch('https://api.example.com');
  // если все хорошо, работаем с данными
} catch(err) {
	// обработка ошибки
}

Также, конструкция try...catch позволяет ловить ошибки из нескольких промисов одновременно.

try {
  let usersRes = await fetch('https://api.example.com/users');
	let users = await usersRes.json();

  let chatsRes = await fetch('https://api.example.com/chats');
	let chats = await chatsRes.json();

  // если все хорошо, работаем с данными
} catch(err) {
	// обработка ошибки
}

Вот, замечательный вариант ловли ошибок. Любая ошибка которая возникнет внутри блока try, попадет в блок catch и мы точно её обработаем.

А точно ли обработаем?

Действительно, а правда ли, что мы обработаем ошибку, или всего лишь сделаем вид? На практике, скорее всего, возникнувшая ошибка будет просто выведена в консоль или т.п. Более того, при появлении ошибки*, интерпретатор прыгнет в блок catch , где не мы, не TypeScript не сможет вывести тип переменной, попавшей туда (пример — возврат с помощью Promise.reject), после чего, произойдет выход из функции. То есть, мы не сможем выполнить код который находится в этом же блоке, но который расположен ниже функции, внутри которой произошла ошибка. Конечно, мы можем предусмотреть такие ситуации, но сложность кода и читаемость вырастут многократно.

Как быть?

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

let [users, err] = await httpGET('https://api.example.com/users');
if (err !== null) {
	// обработка ошибки
}
// продолжаем выполнение кода

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

Пример для вызова нескольких функций возвращающих Promise.

let err: Error,
		users: User[],
		chats: Chat[];

[users, err] = await httpGET('https://api.example.com/users');
if (err !== nil) {
  // обработка ошибки
}

[chats, err] = await httpGET('https://api.example.com/chats');
if (err !== nil) {
  // обработка ошибки
}

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

Давайте рассмотрим как можно реализовать такую функцию и что нам вообще нужно делать. Для начала, давайте определим тип PairPromise. В данном случае, я решил использовать null если результата или ошибки нету, так как он просто короче.

type PairPromise<T> = Promise<[T, null] | [null, Error]>;

Определим возможные возвращаемые ошибки.

const notFoundError = new Error('NOT_FOUND');
const serviceUnavailable = new Error('SERVICE_UNAVAILABLE');

Теперь опишем нашу функцию.

const getUsers = async (): PairPromise<User[]> => {
    try {
        let res = await fetch('https://api.example.com/users');
        if (res.status === 504) {
            return Promise.resolve([null, serviceUnavailable]);
        }

        let users = await res.json() as User[];

        if (users.length === 0) {
            return Promise.resolve([null, notFoundError]);
        }

        return Promise.resolve([users, null]);
    } catch(err) {
        return Promise.resolve([null, err]);
    }
} 

Пример использования такой функции.

let [users, err] = await getUsers();
if (err !== null) {
	switch (err) {
  	case serviceUnavailable:
    	// сервис недоступен
    case notFoundError:
    	// пользователи не найдены
    default:
    	// действие при неизвестной ошибке
	}
}

Вариантов применения данного подхода обработки ошибок очень много. Мы сочетаем удобства конструкции try...catch и Error-First Callback, мы гарантированно поймаем все ошибки и сможем удобно их обработать, при необходимости. Как приятный бонус — мы не теряем типизацию. Также, мы не скованы лишь объектом Error, мы можем возвращать свои обертки и успешно их использовать, в зависимости от наших убеждений.

Очень интересно мнение сообщества на эту тему.

Полное руководство по 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

If you’ve read our related article Recording Outbound Links using
Ajax you’ll be familiar with the concept of using Ajax to record
user actions.

This article goes a step further letting you log any JavaScript
errors that occur on your website to a text file that you can later view or
download.

This can be very handy as many JavaScript errors are user-, browser-
or platform-specific and it’s not always feasible to test all
possiblities yourself.

Defining a Custom Error Handler

The first step is to override or redirect the built in error handler
window.onerror with our own function. This is a lot simpler
than it sounds:

<script src="ajaxrequest.js"></script>
<script>

window.onerror = function(msg, url, line) {
if(encodeURIComponent) {
var req = new AjaxRequest();
var params = "msg=" + encodeURIComponent(msg) + '&url=' + encodeURIComponent(url) + "&line=" + line;
req.setMethod("POST");
return req.loadXMLDoc("/logerror.php", params);
}
return false;
};

</script>

If you return a value of true then only your
function will be called and no message will be presented in the browser.
If you instead return a value of false, your error handler will
be called and then the browser will respond to the error in it’s
own way.

The function as presented will either make a successful call
to the server-side script (see below) or let the browser display
the error to the user as it sees fit. That way someone is always
notified of the error.

The best place for this code is in the HEAD of your HTML document or
in an included JavaScript file.

Upgraded code

The code here serves the same function has that already presented,
but is using a new version of our Ajax class as well as the error event which is slightly different to
the old onerror approach:

<script src="AjaxRequestXML.js"></script>
<script>

window.addEventListener("error", (e) => {
let params = {
msg: e.message,
url: e.filename,
line: e.lineno,
};
(new AjaxRequestXML).post("/logerror.php", params);
});

/*
throw "Deliberate Error!";
*/

</script>

The throw command shown in comments can be
used for testing. It will append something like the following to the
log file:

Uncaught Deliberate Error! in https://www.the-art-of-web.com/javascript/onclick-async-href/ on line XXX

So far all we’ve done is captured the JavaScript error and POSTed the
details to /logerror.php. We still have to enable logging.

Logging errors to a text file

Here we see how a PHP script can be used as the target of the Ajax
request in order to record the details of the error:

Source code for /logerror.php:

<?PHP
include "xmlresponse.php";

if($_POST && isset($_POST['msg'], $_POST['url'], $_POST['line'])) {
if($fp = fopen("{$_SERVER['DOCUMENT_ROOT']}/onerror.log", "a")) {
$logline = "[{_SERVER['REMOTE_ADDR']}]: {$_POST['msg']} in {$_POST['url']} on line {$_POST['line']}";
fwrite($fp, date("M d H:i:s") . " {$logline}\n");
fclose($fp);
}
}

$xml = new \Chirp\xmlResponse();
$xml->start();
$xml->end();

exit;

When it receives a POST request with values for msg,
url and line then a line containing that information
is added to the file onerror.log.

The XML header and empty
response tag are necessary only to prevent a JavaScript error
in the processReqChange — essential in this case as you
don’t want to create an infinite loop.

The onerror.log file needs to be writable by the webserver.
The best way to enable this is to first touch the file so that
it exists, then chgrp the file to the webserver user and
finally chmod 664 to make it writable.

Unfortunately the Safari and Opera web browsers no
longer support the setting of an onerror event handler, so this
script is less useful than it might have been.

Possible additions to the script

If you’re going to use this on a live website then you might want to
consider:

  • checking that the Referer is actually your own website;
  • modifying the fwrite message to include the User Agent string so you can see which browser encountered the error; and
  • monitoring the log file using a CRON script to make sure it’s not growing out of control.

The code presented here works in Internet Explorer (Win) and Firefox,
but not so well in Safari which seems to have it’s own way of handling
errors. Some Mozilla browsers also don’t pass a value for url
to the error handling function.

  • JavaScript Form Validation using Ajax
  • JavaScript Making an asynchronous request before following an href
  • JavaScript Using a Promise to make sequential Ajax requests
  • JavaScript Using XMLHttpRequest to log JavaScript errors
  • JavaScript Making sure form values are unique using Ajax
  • JavaScript Avoiding the Race Condition with Ajax
  • JavaScript Recording Outbound Links using Ajax
  • PHP Generating an XML Response for Ajax Applications
  • JavaScript Web Services using XMLHttpRequest (Ajax)

< JavaScript

Post your comment or question

Понравилась статья? Поделить с друзьями:
  • Xmeye ошибка 400008
  • Xmeye ошибка 10005 что делать
  • Xmeye неизвестная ошибка 100000
  • Xmeye код ошибки 99988
  • Xmeye код ошибки 99987 systeminfo