Try/catch blocks with asynchronous JavaScript with callback functions - javascript

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.

Here's a step-by-step breakdown of what happens in your code.
The code enters the try-catch block.
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.
No exceptions were encountered, so the catch block is skipped.
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.
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).
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.

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();
});
}

Related

Error not catching in the try/catch when remove the await [duplicate]

const errorTest = async() => {
const result = await $.get("http://dataa.fixer.io/api/latest?access_key=9790286e305d82fbde77cc1948cf847c&format=1");
return result;
}
try {
errorTest()
}
catch(err) {
console.log("OUTSIDE ERROR!" + err)
}
The URL is intentionally incorrect to throw an error, but the outside catch() it not capturing it. Why?
If I use then() and catch() instead, it works.
errorTest()
.then(val=> console.log(val))
.catch(err=> console.error("ERROR OCCURRED"))
This works, but the try {..} catch() doesn't. Why?
I keep getting the Uncaught (in promise) error.
async function errorTest() { /* ... */ }
try {
errorTest()
}
catch(err) {
console.log("OUTSIDE ERROR!" + err)
}
Because errorTest is async, it will always return a promise and it is never guaranteed to finish execution before the next statement begins: it is asynchronous. errorTest returns, and you exit the try block, very likely before errorTest is fully run. Therefore, your catch block will never fire, because nothing in errorTest would synchronously throw an exception.
Promise rejection and exceptions are two different channels of failure: promise rejection is asynchronous, and exceptions are synchronous. async will kindly convert synchronous exceptions (throw) to asynchronous exceptions (promise rejection), but otherwise these are two entirely different systems.
(I'd previously written that async functions do not begin to run immediately, which was my mistake: As on MDN, async functions do start to run immediately but pause at the first await point, but their thrown errors are converted to promise rejections even if they do happen immediately.)
function errorTest() {
return new Promise(/* ... */); // nothing throws!
}
function errorTestSynchronous() {
throw new Error(/* ... */); // always throws synchronously
}
function errorTestMixed() {
// throws synchronously 50% of the time, rejects 50% of the time,
// and annoys developers 100% of the time
if (Math.random() < 0.5) throw new Error();
return new Promise((resolve, reject) => { reject(); });
}
Here you can see various forms of throwing. The first, errorTest, is exactly equivalent to yours: an async function works as though you've refactored your code into a new Promise. The second, errorTestSynchronous, throws synchronously: it would trigger your catch block, but because it's synchronous, you've lost your chance to react to other asynchronous actions like your $.get call. Finally, errorTestMixed can fail both ways: It can throw, or it can reject the promise.
Since all synchronous errors can be made asynchronous, and all asynchronous code should have .catch() promise chaining for errors anyway, it's rare to need both types of error in the same function and it is usually better style to always use asynchronous errors for async or Promise-returning functions—even if those come via a throw statement in an async function.
As in Ayotunde Ajayi's answer, you can solve this by using await to convert your asynchronous error to appear synchronously, since await will unwrap a Promise failure back into a thrown exception:
// within an async function
try {
await errorTest()
}
catch(err) {
console.log("OUTSIDE ERROR!" + err)
}
But behind the scenes, it will appear exactly as you suggested in your question:
errorTest()
.then(val=> console.log(val))
.catch(err=> console.error("ERROR OCCURRED"))
You need to await errorTest
const callFunction=async()=>{
try{
const result = await errorTest()
}catch(err){
console.log(err)
}
}
callFunction ()
Note that the await errorTest() function has to also be in an async function. That's why I put it inside callFunction ()
Another Option
const errorTest = async() => {
try{
const result = await $.get("http://dataa.fixer.io/api/latest?access_key=9790286e305d82fbde77cc1948cf847c&format=1");
console.log(result)
}catch(err){
console.log(err)
}
}
I think the fundamental misunderstanding here is how the event loop works. Because javascript is single threaded and non-blocking, any asynchronous code is taken out of the normal flow of execution. So your code will call errorTest, and because the call to $.get performs a blocking operation (trying to make a network request) the runtime will skip errorTest (unless you await it, as the other answers have mentioned) and continue executing.
That means the runtime will immediately jump back up to your try/catch, consider no exceptions to have been thrown, and then continue executing statements which come after your try/catch (if any).
Once all your user code has ran and the call stack is empty, the event loop will check if there are any callbacks that need to be ran in the event queue (see diagram below). Chaining .then on your async code is equivalent to defining a callback. If the blocking operation to $.get completed successfully, it would have put your callback in the event queue with the result of errorTest() to be executed.
If, however, it didn't run successfully (it threw an exception), that exception would bubble up, as all exceptions do until they're caught. If you have defined a .catch, that would be a callback to handle the exception and that'll get placed on the event queue to run. If you did not, the exception bubbles up to the event loop itself and results in the error you saw (Uncaught (in promise) error) -- because the exception was never caught.
Remember, your try/catch has long since finished executing and that function doesn't exist anymore as far as the runtime is concerned, so it can't help you handle that exception.
Now if you add an await before errorTest() the runtime doesn't execute any of your other code until $.get completes. In that case your function is still around to catch the exception, which is why it works. But you can only call await in functions themselves that are prefixed with async, which is what the other commenters are indicating.
Diagram is from https://www.educative.io/answers/what-is-an-event-loop-in-javascript. Recommend you check it out as well as https://www.digitalocean.com/community/tutorials/understanding-the-event-loop-callbacks-promises-and-async-await-in-javascript to improve your understanding of these concepts.

Promises - catch is not working

Why is it that the following code doesn't catch the exception being thrown?
$http.get(null) // <- this throws a fit
.catch(function (e) {
console.log(e); // <- this is not being triggered
});
Error: [$http:badreq] Http request configuration url must be a string or a $sce trusted object. Received: null
https://errors.angularjs.org/1.7.2/$http/badreq?p0=null
.catch() is not a replacement for normal try catch.
It is specifically for handling exceptional circumstances that occurred during the promise resolution process.
In this case, the exception (throwing a fit) is happening outside the promise resolution process.
Your supplying invalid input to the $http.get method causing an exception before an XHR even gets created, not something going wrong with the HTTP request or any subsequent processing.
Here is an equivalent of what is happening:
try {
$http.get(throwAnException())
// .catch isn't even being evaluated!
.catch(function(e) {
console.error(e); // no chance of being called
});
} catch (e) {
// I would be evaluated
console.error(e);
}
function throwAnException() {
throw "An error before we even start";
}
You need to understand that this catch is waiting for a "rejection" from your get call.
In other words, your $http.get is triggering an error and never returning a promise...this way, you can't execute a catch straight from an error, get it?
If you have $http.get("xyz") it will then do its thing and reject, therefore, being caught by your catch.
What you are doing results in this
// step 1
$http.get(null)
.catch()
// step 2
ERROR
.catch() // will not even get here, but if it did, it wouldn't work either
While, if your get could work, but rejected, you would have:
// step 1
$http.get('someFailingURL')
.catch()
// step 2
RejectedPromise
.catch() // gonna work :)
If your url comes from a different source (and that's why you get a null value for it some times) you should probably validate it before trying and getting it, like so:
if (yourVariableURL !== null) {
$http.get(yourVariableURL)
.catch()
} else {
console.log('Invalid url');
}
This will throw Error: $http:badreq Bad Request Configuration. It has issue at request parameter level where string/url is expected but not null. Hence not going inside the block. That is the reason it is not triggering catch.
The error will be thrown by Angular is as below -
Http request configuration url must be a string or a $sce trusted object. Received: null
This error occurs when the request configuration parameter passed to the $http service is not a valid object. $http expects a single parameter, the request configuration object, but received a parameter that was not an object or did not contain valid properties.
To resolve this error, make sure you pass a valid request configuration object to $http.
Additionally, if required to catch the issue with this block of code itself, wrap it in try-catch block.

Throw error based on result of promise

The below code doesn't handle error though condition is satisfied. Please help understand why
Edit:the below code now terminates code flow in case of satisfying condition but throwing a error result in unhandled rejection.
utils.js
const isMaximum = Id => {
return db.Entries
.findAndCountAll()
.then(counts => {
let numberOfEntries = 2;
let maxEntries = 2;
if (numberOfEntries == maxEntries) {
return true;
}
else{
return false;
}
});
};
xyz.js
const doSomething=(req,res,next)=>{
Utils.isMaximum(id).then(isLimit=> {
if(isLimit) throw new Error("not allowed"); //unhandled rejection
//rest of code gets terminated as expected
});
}
Related questions Why can I not throw inside a Promise.catch handler? couldn't find me a solution. So please make it clear.
Throwing inside a .catch() handler makes the promise chain a rejected promise. If you don't have a subsequent .catch() handler, then it will get reported as an unhandled promise rejction because you have no .catch() afterwards to catch the rejected promise. This is nearly always a programming error which is why you get the warning.
Throwing inside a .catch() does not terminate your program. That exception is caught by the promise infrastructure (and turned into a rejected promise). That's how promises work (by specification). If you want your program to terminate at that point, then you can call process.exit() rather than throwing.
Now you've edited your question and now you're trying to throw inside a .then() handler. OK. I'll address what you've added. Next time please start with the whole code context so we can more quickly understand the situation.
Utils.isMaximum() is asynchronous. It returns a promise. You need to program with using asynchronous techniques. You can't think like synchronous sequential code. Any code that comes after your call to Utils.isMaximum() will execute BEFORE the .then() handler is called.
Here's a simple example:
Utils.isMaximum(id).then(isLimit => {
console.log("A");
});
console.log("B");
That will output:
B
A
So, for starters, you can't prevent the code that comes after the .then() block from running by anything you do inside the .then() block because the rest of the function executes BEFORE the .then() handler does.
So, the usual way you deal with that is you have to put the rest of the code INSIDE the .then() handler or inside a function you call from there:
const doSomething=(req,res,next)=>{
Utils.isMaximum(id).then(isLimit=> {
if(!isLimit){
// rest of code here that sends regular response
} else {
// handle error here - send error response
// or OK to throw here, throw will be caught by next .catch()
}
}).catch(err => {
// send error response here
});
}
I didn't catch the error I throw and that is the only reason why. Below resolved.
const doSomething=(req,res,next)=>{
Utils.isMaximum(id).then(isLimit=> {
if(isLimit) throw new Error("not allowed");
}).catch(next);
//rest of code gets terminated as expected
}

Strategies for handling exceptions between ticks (or, stacks) in Node.js? [duplicate]

This question already has answers here:
Is it possible to catch exceptions thrown in a JavaScript async callback?
(7 answers)
Closed 9 years ago.
I've got the following:
var http = require('http');
server = http.createServer(function(request, response) {
try {
// Register a callback: this happens on a new stack on some later tick
request.on('end', function() {
throw new Error; // Not caught
})
throw new Error; // Caught below
}
catch (e) {
// Handle logic
}
}
Now, the first Error gets caught in the try...catch, but the second Error does not appear to be getting caught.
A couple questions:
Does the second Error not get caught because it occurs on a different stack? If so, am I to understand that try...catch behavior is not lexically bound, but depends instead on the current stack? Am I interpreting this correctly?
Are there any well-explored patterns to handle this type of problem?
You could also use a simple EventEmitter to handle an error in a case like this.
var http = require('http');
var EventEmitter = require("events").EventEmitter;
server = http.createServer(function(request, response) {
var errorHandler = new EventEmitter;
// Register a callback: this happens on a new stack on some later tick
request.on('end', function() {
if(someCondition) {
errorHandler.emit("error", new Error("Something went terribly wrong"));
}
});
errorHandler.on("error", function(err) {
// tell me what the trouble is....
});
});
The second error is never thrown, nor caught. You throw an error before you add your anonymous function as a handler for request's end event.
Even if you had, it isn't going to be caught, as it is indeed on a different stack. You must raise an error event if you want to work this way, or simply pass error as the first callback parameter to any callback function. (These are both common conventions, not mandates.)
catch catches Errors that are thrown while executing the content of its try block. Your 'end' handler is created and assigned inside the try block, but it obviously doesn't execute there. It executes when request fires its end event, and the try block will be long forgotten by then.
You'll generally want to catch errors within your event handler and not let them percolate up through the stack. You'll never/rarely want to throw them.
As a last resort, you can register an handler for 'uncaughtException' on process, which will catch all exceptions that aren't caught elsewhere. But that's rarely the right solution. http://nodejs.org/api/process.html#process_event_uncaughtexception

Catch statement does not catch thrown error

For some reason this code gives me an uncaught exception error. It seems the catch block is not catching the error. Are try catch blocks scoped in such a way that I cannot throw an error in a nested function, and then expect it to be caught by a catch statement scoped higher up the chain? Some of the sensitive data with in the application that i'm working in has been removed, but it expected that leadInfo[ 0 / 1] would be a 32 character alpha numeric string that I pull from URL parameters.
The underlying issue here is with my AJAX call returning an error from the API and that error not being handled properly within the application. Hence the need for the throw statement. The AJAX call completes fine, and returns a JSON object that does not contain the email address as a property, so I need to handle that in a way that changes the page to reflect that.
jQuery(document).ready(function(){
try {
url = "http://api.com/api/v1/lead/" + leadInfo[1]
jQuery.ajax({
type: 'GET',
contentType: 'application/json',
url: url,
dataType : 'jsonp',
success: function (result) {
result = jQuery.parseJSON(result);
if(!result.data.email){
throw ('New exception');
}
console.log(result);
jQuery('.email').html(result.data.email);
}
});
jQuery('.surveryButton').click(function(){
window.location.replace("http://" + pgInventory.host + pgInventory.path + leadInfo[0] + "&curLeadId=" + leadInfo[1] + "&curViewedPages=0");
});
}
catch(err) {
jQuery('.email').html('your e-mail address');
jQuery('#arrowContent').remove();
}
});
The reason why your try catch block is failing is because an ajax request is asynchronous. The try catch block will execute before the Ajax call and send the request itself, but the error is thrown when the result is returned, AT A LATER POINT IN TIME.
When the try catch block is executed, there is no error. When the error is thrown, there is no try catch. If you need try catch for ajax requests, always put ajax try catch blocks inside the success callback, NEVER outside of it.
Here's how you should do it:
success: function (result) {
try {
result = jQuery.parseJSON(result);
if (!result.data.email) {
throw ('New exception');
}
console.log(result);
jQuery('.email').html(result.data.email);
} catch (exception) {
console.error("bla");
};
}
Due to the asynchronous nature of the callback methods in javascript, the context of the function throwing the error is different compared to the original one. You should do this way:
success: function (result) {
try {
result = jQuery.parseJSON(result);
if(!result.data.email){
throw ('New exception');
}
console.log(result);
jQuery('.email').html(result.data.email);
}
catch(err) {
// Dealing with the error
}
}
I would suggest you to have a look at this excellent article about the (very particular) contexts, closures and bindings in Javascript.
The problem is that ajax is asynchronous by definition. Your exception does not get thrown from within the $.ajax function, but from the callback function on success (which is triggered at a later time).
You should give an error: function(data) {} parameter to it as well, to handle server response errors, and furthermore you should place the try/catch block inside the callback function.
If you really want to catch it outside the callback, then you should consider calling a function rather than throwing an exception, because I don't see how it can be done.

Categories