Exception handling is a place that must be paid attention to during program operation. When an exception occurs, it should be paid attention to in the first time and solved quickly. Most programmers is percentage of code to the right, so you should when writing code to do prevention with abnormal in advance, to ensure that in the presence of abnormal, give users a friendly reminder, request timeout not suspend service results, and can record abnormal information, convenient late trying to solve.

One. Synchronization code exception capture processing

1. Exceptions in synchronized code can be caught and processed by using the try{}catch structure.

try {  
 throw new Error('Error message');
} catch (e) {  
 console.error(e.message);
}Copy the code

It can be captured normally.

Error handling of asynchronous code

1. Try/catch interface

What about using the try{}catch structure in asynchronous code?

try {  
  setTimeout((a)= >{
    throw new Error('Error message'); })}catch (e) {
  console.error('error is:', e.message);
}Copy the code

Execution Result:

However, no asynchronous errors were caught.

2. UncaughtException of process

What about asynchronous errors? First of all, change your mind, because exceptions are not prepared in advance and you cannot control where they occur, so take a higher perspective, such as listening for error exceptions in the application process, so as to catch unexpected error exceptions and ensure that the application does not crash.

process.on('uncaughtException', (e)=>{  
  console.error('process error is:', e.message);
});Copy the code

The code listening for uncaughtException from Process can catch the error message that the entire process contains in the asynchron to ensure that the application does not crash.

However, a new problem arises, because when an exception occurs unexpectedly, it interrupts directly from the corresponding execution stack, and under the exception event captured by Process, the garbage collection function of the V8 engine does not follow the normal process, and then starts to leak memory.

Memory leaks are also a serious problem that can’t be ignored, and process.on(‘uncaughtException’) can’t be guaranteed to cause memory leaks. So when an exception is caught, explicitly manually kill the process and restart the Node process to ensure that the memory is released and the service is available in the future.

process.on('uncaughtException', (e)=>{  
  console.error('process error is:', e.message);  
  process.exit(1);  restartServer(); // Restart the service
});Copy the code

However, the above approach is a bit straightforward, we can not help wondering, in a single-process single-instance deployment, kill process during the restart of the service is not available. This is clearly unreasonable.


3. Use the Domain module

The domain module, which processes multiple DIFFERENT IO operations as a group. Register events and call-back to the domain. The Domain object is notified when an error event occurs or an error is thrown, without losing context or causing the program to exit immediately with an error, unlike process.on(‘uncaughtException’).


Domain modules can be divided into implicit binding and explicit binding: Implicit binding: automatically binds variables defined in the Domain context to Domain objects. Explicit binding: binds variables not defined in the Domain context to Domain objects in code

const needSend = { message: 'Some information to pass to error handling' };
d.add(needSend);
function excute() {
  try {    
    setTimeout((a)= >{     
     throw new Error('Error message');    
    });  
  } catch (e) {   
    console.error('error is:', e.message); }}; d.run(excute);Copy the code



Domin has the obvious advantage of being able to pass some information to the error handler when something goes wrong, and can do some reporting and other processing work, at least to ensure that the service after the restart, the program monkey know what happened, have clues to look up, can also choose to pass the context in, do some follow-up processing. For example, when the service fails, the user request stack information can be sent downstream to inform the user of the service exception instead of waiting for the request to automatically timeout.

. d.add(res); . d.on('error', (err) => {  
  console.log('err', err.message);  
  res.end('Server is abnormal, please try again later! ');
});Copy the code

But as with process.on(‘uncaughtException’), it is difficult to ensure that it does not leak memory.

In addition, in the official document, the domain module handles the abandoned state, but there is no other solution to completely replace the domain module, but MY current node10 version is still available, so I should not worry about the abandoned domain module for the time being.


Three. Multi-process mode plus exception capture after restart

The above approach is not a perfect solution to the problem. How can exceptions occur without crashing, catch exceptions without causing memory leaks, and restart to release the cache without causing the service to become unavailable?

A better solution is to multiple processes (cluster) pattern to deploy application, when a process is exception handling, dot can make a report after, began to restart the free memory, while the other request is accepted, other processes can still provide service, of course, the premise is you can’t count is an unusually large number of applications.

The following is the use of cluster and domain together, in the way of multi-process to ensure that the service is available, while the error information can be passed down to report, and retain the context of the error, return to the user request, do not let the user request timeout, and then manually kill the abnormal process, and then restart.

const cluster = require('cluster');
const os = require('os');
const http = require('http');
const domain = require('domain');
const d = domain.create();
if (cluster.isMaster) {  
  const cpuNum = os.cpus().length;  
  for (let i = 0; i < cpuNum; ++i) {    
    cluster.fork()  
  }  
  cluster.on('fork', worker=>{    
    console.info(`The ${new Date()} worker${worker.process.pid}The process started successfully);  
  });  
  cluster.on('exit',(worker,code,signal)=>{    
    console.info(`The ${new Date()} worker${worker.process.pid}The process starts abnormally and exits); cluster.fork(); })}else {  
  http.createServer((req, res) = >{   
    d.add(res);    
    d.on('error', (err) => {      
      console.log('Err information recorded', err.message);      
      console.log('Error with work ID :', process.pid);      
      UploadError (err) // uploadError(err) // uploadError to monitor
      res.end('Server exception, please try again later');      
      // Manually restart the abnormal child process
      cluster.worker.kill(process.pid);    
    });    
    d.run(handle.bind(null, req, res));  
  }).listen(8080);
};
function handle(req, res) {  
  if (process.pid % 2= = =0) {    
    throw new Error('Wrong');  
  }  
  res.end(`response by worker: ${process.pid}`);
};Copy the code

Interested students strongly recommend to copy the code to the local debugging observation.

The above content is their own summary, there will be mistakes or understanding bias, if there are questions, I hope you leave a message to correct, so as not to mistake people, if there are any questions please leave a message, we will try our best to answer it. If it’s helpful, don’t forget to share it with your friends or click “Watching” in the lower right corner. You can also follow the author, view historical articles and follow the latest developments to help you become a full stack engineer!