49

Handling errors in JavaScript with try...catch and finally

 5 years ago
source link: https://www.tuicool.com/articles/hit/Z3mEraB
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Mistakes happen. That’s a given. According to Murphy’s law, whatever can go wrong, will go wrong. Your job, as a programmer, is to be prepared for that fact. You have a set of tools that are prepared to do precisely that. In this article, we go through them and explain how they work.

An error

When a runtime error occurs, a new error object is created and thrown . The browser presents you with the file name and the line number in which the problem occurs. Time for some evildoing:

cosnole.log('Hello world!');

I’ve put that code into my DevTools console and got that:

JJbiIj3.png!web

The thing that you might find striking here is the VM164 . If you look it up in the chromium source code you discover that it has no special meaning and occurs if your code is not tied to a particular file. The same thing happens if you are using  eval (which you probably shouldn’t do!). Otherwise, it is the name of the file in which the error occurs.

In this example, the ReferenceError is the message name and cosnole is not defined is the message.

Since we refer here to throwing the error, how about we catch it?

Try & catch

If an error is thrown inside of the try block, the execution of the rest of the code is stopped. The error is caught so that you can deal with it inside of the  catch block. If no exceptions are thrown in the  try block, the  catch is skipped.

try {
  cosnole.log('Hello world!');
} catch(error) {
  console.log(error.name); // ReferenceError
  console.log(error.message); // cosnole is not defined
}

Even though back in the days all declared variables used to have a functional scope (since they were declared using the var keyword, not with const and  let ), the  catch  statement is an example of block scope. The  catch block receives an identifier called  error in the example above. It holds the value of what was thrown in the  try block. Since it is a block-scoped, after the  catch block finishes executing, the error is no longer available.

If you would like to know more about variable declarations in JavaScript, check out Scopes in JavaScript. Different types of variable declarations 

An important thing to keep in mind is that if you don’t provide a variable name for the error in the catch statement, an error is thrown!

j6Ffyey.png!web

Try…catch works only for runtime errors. If the error comescame from the fact that your code is not valid JavaScript, it won’t be caught.

mAzamyY.png!web

Asynchronous code

The try…catch mechanism can’t be used to intercept errors generated in asynchronous callbacks. This fact is a common source of a misunderstanding.

try {
  setTimeout(() => {
    cosnole.log('Hello world!'); 
  });
} catch(error) {
  console.log(error);
}
Uncaught ReferenceError: cosnole is not defined at setTimeout

It does not work because the callback function passed to the setTimeout is called asynchronously. By the time it runs, the surrounding try…catch blocks have been already exited. You need to put them in the callback itself.

It is a lot better if you use promises.  You can catch your errors using the catch function:

function failingFunction(){
  return new Promise(() => {
    cosnole.log('Hello world!');
  })
}
 
failingFunction()
  .catch(error => {
    console.log(error);
  });

If you would like to know more about promises and callbacks check out Explaining promises and callbacks while implementing a sorting algorithm

The try…catch blocks make a comeback to deal with asynchronous code if you use async/await

function failingFunction(){
  return new Promise(() => {
    cosnole.log('Hello world!');
  })
}
 
(async function() {
  try {
    await failingFunction();
  } catch(error){
    console.log(error);
  }
})()

If you are interested in async/await, read Explaining async/await. Creating dummy promises

Finally

There is one more clause that you can use, and it is finally . It will execute regardless of whether an exception is thrown.

let isLoading = true;
try {
  const users = await fetchUsers();
  console.log(users);
} catch(error){
  console.log('Fetching users failed', error);
} finally {
  isLoading = false;
}

If you use the try block, you need to follow it with either the  catch statement, the finally block or both. You might think that the finally statement does not serve any purpose, because you can write code just under the try…catch block. The fact is it is not always going to be executed. Examples of that are nested try-blocks.

try {
  try {
    cosnole.log('Hello world!'); // throws an error
  }
  finally {
    console.log('Finally');
  }
  console.log('Will I run?');
}
catch (error) {
  console.error(error.message);
}

In this example, the console . log ( 'Will I run?' ) is not executed, because control is immediately transferred to the outer try’s catch-block. The finally is executed first even in such a case.

For more examples on how finally works, check out the code presented on MDN .

Throwing exceptions

JavaScript allows us to throw our own exceptions using the throw keyword. There is no restriction on the type of data that you throw.

try {
  throw 'Not good!';
} catch(error) {
  console.log(error);
}

The Error object

The error that gets thrown when the runtime error occurs inherits from Error.prototype and consists of two properties: the name of the error and the message. The name can be one of the predefined types of errors or a custom one.

The Error.prototype.constructor accept one argument, and it is the error message. If you would like to throw a non-generic error, you can use one of the predefined ones, or define a new one! To do this, you can use ES6 classes:

class MyError extends Error {
  constructor(message) {
    super(message);
    this.name = 'MyError';
  }
}
 
throw new MyError('Not good!');

Nzm2YfF.png!web

As you can see, the name of the error is now MyError . In Chrome it would be named after you class anyway, even without doing  this . name = 'MyError' , but it might not work that way in other browsers. When an error is thrown, most browsers display a whole stack so that you can see what exactly happen:

class MyError extends Error {
  constructor(message) {
    super(message);
    this.name = 'MyError';
  }
  toString() {
    return 'The overriden error message';
  }
}
 
function outerFunction() {
  innerFunction();
}
 
function innerFunction() {
  throw new MyError('Not good!');
}
 
outerFunction();

Arm2eqj.png!web

The Error.protototype.stack is non-standard, but it is implemented both by Chrome and Firefox.

try {
  outerFunction();
} catch(error) {
  console.log(error.stack);
}

Since it is not standardized, it behaves differently across browsers, so watch out!

7zENnef.png!web

Summary

In this article, we went through handling errors in JavaScript. It included describing how errors work in JavaScript and how to throw them. We’ve also covered the try…catch block and what purpose can the finally block serve. Aside from that, we’ve also explained the Error object in JavaScript and what are its properties, including some non-standardized ones. Hopefully, this will help you handle your errors better.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK