1

I have a piece of code:

function backgroundReadFile(url, callback) {
  var req = new XMLHttpRequest();
  req.open("GET", url, true);
  req.addEventListener("load", function() {
    if (req.status < 400)
      callback(req.responseText);
  });
  req.send(null);
}
try {
  backgroundReadFile("example/data.txt", function(text) {
    if (text != "expected")
      throw new Error("That was unexpected");
  });
} catch (e) {
  console.log("Hello from the catch block");
}

In my console I get

Error: That was unexpected (line 13)

which is just fine. But, I am told that:

In the code, the exception will not be caught because the call to backgroundReadFile returns immediately. Control then leaves the try block, and the function it was given won’t be called until later.

The question is: why other errors will not be caught here? When we have, say, connection problems, or the file does not exist? As far as I can see, the callback function won`t execute if

req.addEventListener("load") 

is not triggered, for example. But it still does - I still get the same error - Error: That was unexpected (line 13).

What does it mean - "exception will not be caught because the call to backgroundReadFile returns immediately"?

Thank you.

2
  • "Told" by whom? Commented Apr 25, 2018 at 12:35
  • It is a passage from "Eloquent JavaScript", and the code is also from this book. Commented Apr 26, 2018 at 12:36

2 Answers 2

2

Here's a step-by-step breakdown of what happens in your code.

  1. The code enters the try-catch block.
  2. backgroundReadFile is called, with two parameters: "example/data.txt", and an anonymous function.
  3. backgroundReadFile creates an AJAX request and calls send(). Here's where the concept of asynchrony comes into play: the actual HTTP request is not sent right away, but rather placed in a queue to be executed as soon as the browser has finished running whatever code it is running at the moment (i.e. your try-ctach block).
  4. backgroundReadFile has thus finished. Execution returns to the try-catch block.
  5. No exceptions were encountered, so the catch block is skipped.
  6. The code containing the try-catch block has finished execution. Now the browser can proceed to execute the first asynchronous operation in the queue, which is your AJAX request.
  7. The HTTP request is sent, and once a response is received, the onload event handler is triggered -- regardless of what the response was (i.e. success or error).
  8. The anonymous function you passed to backgroundReadFile is called as part of the onload event handler, and throws an Error. However, as you can see now, your code is not inside the try-catch block any more, so it's not caught.

TL;DR: The function that throws the Error is defined inside the try-catch block, but executed outside it.

Also, error handling in AJAX requests has two sides: connection errors and server-side errors. Connection errors can be a request timeout or some other random error that may occur while sending the request; these can be handled in the ontimeout and onerror event handlers, respectively. However, if the HTTP request makes it to the server, and a response is received, then as far as the XMLHttpRequest is concerned, the request was successful. It's up to you to check, for example, the status property of the XMLHttpRequest (which contains the HTTP response code, e.g. 200 for "OK", 404 for "not found", etc.), and decide if it counts as successful or not.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks a lot. But when exactly the request is sent when making it asynchronously? There is this queue, and in my code the request is sent when the try/catch block is over. What about other instances? Will the send() be sent when a current function is over, for example? In other words, as far as I see it, everything that is after send() and the actual call to the backgroundReadFile will be executed first (i.e. prior to the backgroundReadFile actual call), when we talk about asynchronous requests?
It's called the "event loop". Basically, there are three ways javascript code can be started: 1. when the browser processes a <script> tag, 2. when an event is triggered, or 3. when you invoke and asynchronous operation. In case 1, javascript will look in the queue after the chunk of code inside the <script> tag has finished running. In case 2, it's after the event handler function has returned, and in case 3 it's after the async operation finished. Here's an article from MDN that explains it much better: developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
2

Your backgroundReadFile function has two parts: A synchronous part, and an asynchronous part:

function backgroundReadFile(url, callback) {
  var req = new XMLHttpRequest();             // Synchronous
  req.open("GET", url, true);                 // Synchronous
  req.addEventListener("load", function() {   // Synchronous
    if (req.status < 400)                     // *A*synchronous
      callback(req.responseText);             // *A*synchronous
  });
  req.send(null);                             // Synchronous
}

You're quite right that an error thrown by the synchronous part of that function would be caught by your try/catch around the call to it.

An error in the asynchronous part will not by caught by that try/catch, because as someone told you, by then the flow of control has already moved on.

So it could be perfectly reasonable to have a try/catch around the call to that function, if it may throw from its synchronous code.


Side note: If you're going to use the callback style, you should always call back so the code using your function knows the process has completed. One style of doing that is to pass an err argument to the callback as the first argument, using null if there was no error, and then any data as a second argument (this is often called the "Node.js callback style"). But in modern environments, a better option would be to use a Promise. You can do that with minimal changes like this:

function backgroundReadFile(url) {
  return new Promsie(function(resolve, reject) {
    var req = new XMLHttpRequest();
    req.open("GET", url, true);
    req.addEventListener("load", function() {
      if (req.status < 400) {
        resolve(req.responseText);
      } else {
        reject(new Error({status: req.status}));
    });
    req.addEventListener("error", reject);
    req.send(null);
  });
}

...which you use like this:

backgroundReadFile(url)
    .then(function(text) {
        // Use the text
    })
    .catch(function(err) {
        // Handle error
    });

But in the specific case of XMLHttpRequest, you could use fetch instead, which already provides you with a promise:

function backgroundReadFile(url) {
  return fetch(url).then(response => {
      if (!response.ok) {
          throw new Error({status: response.status});
      }
      return response.text();
  });
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.