Reputation: 121
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 thetry
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.
Upvotes: 1
Views: 1064
Reputation: 4116
Here's a step-by-step breakdown of what happens in your code.
backgroundReadFile
is called, with two parameters: "example/data.txt"
, and an anonymous function.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).backgroundReadFile
has thus finished. Execution returns to the try-catch block.onload
event handler is triggered -- regardless of what the response was (i.e. success or error).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.
Upvotes: 2
Reputation: 1074258
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();
});
}
Upvotes: 2