Like any language, Javascript will also encounter error situations such as: undefined, null, JSON.parse () fail …
This means that we have to handle these exceptions in a nutshell if we want to have a good user experience for our application.
The solution to this problem is to use try...catch
blocks, which also helps our application not to crash when an error occurs.
Try … Catch
To use try...catch
block, we use the following syntax:
1 2 3 4 5 6 7 8 9 10 11 12 13 | try{ // các đoạn xử lý mà có thể phát sinh ngoại lệ // phải có ít nhất 1 dòng code trong khối này } catch (error){ // xử lý lỗi ngoại lệ ở đây // khối này có thể không cần nếu khối finally đươck khai báo } finally { // phần này có thể có hoặc không // khối này sẽ được chạy sau khi xử lý xong khối try hoặc catch } |
An example to make it easier to imagine:
1 2 3 4 5 6 | try { undefined.prop } catch (error) { console.log(error); } |
In the above example, when we took the attribute from undefined
, an exception would be raised. In the catch
block, we capture the error event and log it out, resulting in the following:
TypeError: Cannot read property ‘prop’ of undefined
When the application runs the undefined.prop command, we get the error message log out instead of crashing the application.
As in the comments above, the try
block must have at least one handler followed by the catch
block or finally
block declared. This means we can have the following cases:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | try { ... } catch { ... } try { ... } finally{ ... } try { ... } catch { ... } finally { ... } |
The catch
block contains code snippets that specify what the app will handle when an exception is thrown in the try
block. If the try
block generates an error, the catch
block will be run; if the try
block does not generate an error, the catch
block will be ignored.
The finally
block will be run after all the code in the try
block or catch
block has finished running. It always runs whether or not an exception is raised.
The try
block may be nested inside each other. If the internal try..catch
block does not handle the error catch , the external try...catch
block must do that. An example to make it easier to imagine:
1 2 3 4 5 6 7 8 9 10 | try { try { undefined.prop } finally { console.log('Inner finally block runs'); } } catch (error) { console.log('Outer catch block caught:', error); } |
When running the above code, we will see the log out:
Inner finally block runs
Outer catch block caught: TypeError: Cannot read property ‘prop’ of undefined ‘
It can be seen that when the internal try...catch
block did not catch an error, the outer try...catch
block had a catch declaration so it handled the error, and the inner finally
block ran before the outer catch
block. Try...catch...finally
runs sequentially from top to bottom in order.
The above catch
blocks are all in the unconditional manner, meaning they can catch all exceptions that occur. The Object error
holds data about an exception error. It only holds data inside the catch
block (it is of type variable scope), if you want to use error object
outside the catch
block, you must assign it to an external variable. After the catch
block has finished running, the error object
will be freed from memory.
The finally
block contains the code that is run after the code in the try
block or catch
block runs, and it is executed regardless of the exception or not. So the finally
block makes the user experience the least affected when our application fails (or maybe due to objective factors such as network connectivity loss …). For example, we can put clean code up, for example, close files when reading files, whether or not there are errors when reading. When running to the line that generates an exception error, the entire code after that line in the try
block will not be executed anymore, so if we handle the close file at the end of the try
block then it may not be executed ( if an error occurs). We can only set the code that will run without caring whether an error is generated, such as cleanup code, release memory … in the finally
block, then we will not have to dupplicate the code when we have to handle things. there in the try and catch block. Illustration:
1 2 3 4 5 6 7 8 9 10 | openFile(); try { // tie up a resource writeFile(data); } finally { closeFile(); // always close the resource } |
We can nest multiple try...catch
blocks together:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | try { try { throw new Error('error'); } finally { console.log('first finally runs'); } try { throw new Error('error2'); } finally { console.log('second finally runs'); } } catch (ex) { console.error('exception caught', ex.message); } |
In the above example we will not see the log out:
second finally runs
because the first try...catch
block does not catch an exception, so when an error occurs, the error is passed and caught by the outer try...catch
block. If we want the second try...catch
block to be executed, we must add the catch
block to the first try...catch
block, as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | try { try { throw new Error('error'); } catch { console.log('first catch block runs'); } finally { console.log('first finally runs'); } try { throw new Error('error2'); } finally { console.log('second finally runs'); } } catch (ex) { console.error('exception caught', ex.message); } |
We can also rethrow the exception errors that are caught in the catch
block. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 | try { try { throw new Error('error'); } catch (error) { console.error('error', error.message); throw error; } finally { console.log('finally block is run'); } } catch (error) { console.error('outer catch block caught', error.message); } |
As the above example, we have 3 outputs
error error, finally block is run outer catch block caught error
because the catch
block of the inner try...catch
block throws the exception error again after logging it out, so the external catch
block is still executed due to a rethrow error from the inner catch
block.
Because the code runs sequentially, we can use the return expression at the end of the try
block. For example, if we want to parse a JSON string into an object, we want to return an empty object if something goes wrong.
1 2 3 4 5 6 7 8 9 | const parseJSON = (str) => { try { return JSON.parse(str); } catch { return {}; } } |
And we test the function above:
1 2 3 | console.log(parseJSON(undefined)); console.log(parseJSON('{"a": 1}')) |
we will be empty on the first line and {a: 1}
on the second line.
Try … Catch in asynchronous handling
We often use async – await for asynchronous processing in Javascript, and we can also use try...catch
blocks to catch rejected promises and handle rejected promises gracefully. For example:
1 2 3 4 5 6 7 8 9 10 | (async () => { try { await new Promise((resolve, reject) => { reject('something wrong!') }) } catch (error) { console.log(error); } })(); |
In the above code, because we reject the promise in the try
block, the catch
block catches the error in the rejected promise and logs out:
something wrong!
The above function looks similar to the normal try...catch
block, but that is not true, since this is an async funcion.
An async function only returns promises, so we cannot return anything instead of promises in the try...catch
block. The catch
block in the async function is just an abbreviation for the catch
function, which is chained with the then function. The above code is equivalent to the following writing in the promises world:
1 2 3 4 5 6 7 | (() => { new Promise((resolve, reject) => { reject('error') }) .catch(error => console.log(error)) })() |
Similarly, the finally
block of try...catch
in async is equivalent to writing the finally function in the promise . We have the following 2 code is equivalent:
1 2 3 4 5 6 7 8 9 10 11 12 | (async () => { try { await new Promise((resolve, reject) => { reject('error') }) } catch (error) { console.log(error); } finally { console.log('finally is run'); } })(); |
1 2 3 4 5 6 7 8 | (() => { new Promise((resolve, reject) => { reject('error') }) .catch(error => console.log(error)) .finally(() => console.log('finally is run')) })() |
The above nested try...catch
rule also applies to the async function, so we can write the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | (async () => { try { await new Promise((resolve, reject) => { reject('outer error') }) try { await new Promise((resolve, reject) => { reject('inner error') }) } catch (error) { console.log(error); } finally { } } catch (error) { console.log(error); } finally { console.log('finally is run'); } })(); |
This makes it easy to nest promises and handle errors according to their scope. This way the code is cleaner and more readable than chaining the hashes , catches and finally function of promises .
summary
To handle exception errors in JavaScript, we use try...catch...finally
block to catch the error. This method can be applied to both synchronous and asynchronous code. We let the code can generate errors in the try
block, and write the exception handling code in the catch
block. In the finally
block, we write the executable code regardless of whether an error occurs or not. Async functions can use try...catch
blocks but they return promises just like other async funciton, but try...catch
blocks in normal functions can return anything.
Source: https://medium.com/javascript-in-plain-english/handling-exceptions-in-javascript-f14d855be11c