Correct way to handle errors in JavaScript, are you using it right?

JavaScript's event-driven paradigm adds rich language and makes programming with JavaScript more diverse. If you think of the browser as an event-driven tool for JavaScript, then when an error occurs, an event will be thrown. Theoretically, these errors can be thought of as simple events in JavaScript.

This article will discuss error handling in client-side JavaScript. It mainly introduces mistakes, error handling, asynchronous code writing, etc. in JavaScript.

Let's take a look at how to properly handle errors in JavaScript.

 

Demo presentation

The demo used in this article can be found on GitHub , after running it will look like this page:

Each button throws an "Exception" which simulates a TypeError being thrown. Here is the definition of the module:

// scripts/error.js
function error() {
  var foo = {};
  return foo.bar();
}

First, the function declares an empty object foo. Note that bar( ) is not defined anywhere. Next verify that this unit test throws an "error":

// tests/scripts/errorTest.js
it('throws a TypeError', function () {
  should.throws(error, TypeError);
});

This unit test is in Mocha , and there are test declarations in  Should.js . Mocha is the test runner and Should.js is the assertion library. This unit test runs on Node and does not require a browser.

error( ) defines an empty object and then tries to access a method. Because bar( ) does not exist within the object, an exception is thrown. This kind of bug that happens to a dynamic language like JavaScript, everyone probably encounters!

 

Error handling (1)

The above error is handled by the following code:

// scripts/badHandler.js
function badHandler(fn) {
  try {
    return fn();
  } catch (e) { }
  return null;
}

The handler takes fn as an input parameter, and then fn is called inside the handler function. Unit tests will reflect the above error handlers:

// tests/scripts/badHandlerTest.js
it('returns a value without errors', function() {
  var fn = function() {
    return 1;
  };
  var result = badHandler (fn);
  result.should.equal(1);
});

it('returns a null with errors', function() {
  var fn = function() {
    throw new Error('random error');
  };
  var result = badHandler (fn);
  should(result).equal(null);
});

If there is a problem, the error handler will return null. The fn( ) callback function can point to a valid method or error.

The following click events continue event processing:

// scripts/badHandlerDom.js
(function (handler, bomb) {
  var badButton = document.getElementById('bad');
  if (badButton) {
    badButton.addEventListener('click', function () {
      handler(bomb);
      console.log('Imagine, getting promoted for hiding mistakes');
    });
  }
}(badHandler, error));

This way of handling hides a bug in the code and is hard to spot. Hidden bugs can take hours of debugging. Especially in multi-layered solutions with deep call stacks, this bug can be harder to spot. So this is a poor way of handling errors.

 

Error handling (2)

Here's another way to handle errors.

// scripts/uglyHandler.js
function uglyHandler(fn) {
  try {
    return fn();
  } catch (e) {
    throw new Error('a new error');
  }
}

The way to handle exceptions is as follows:

// tests/scripts/uglyHandlerTest.js
it('returns a new error with errors', function () {
  var fn = function () {
    throw new TypeError('type error');
  };
  should.throws(function () {
    uglyHandler(fn);
  }, Error);
});

以上对错误的处理程序有明显的改进。在这里异常会调用堆栈进行冒泡。同时错误会展开堆栈,这对调试非常有帮助。除了抛出异常,解释器还会沿着栈寻找另外的处理。这也带来了可以从堆栈顶部处理错误的可能。但这还是一种较差的错误处理,需要我们从堆栈中一步步追溯原始的异常。

可以采用一种替代方案,用自定义的错误方式来结束这种较差的错误处理。当你向错误中添加更多详细信息时,会让这种方法变得很有帮助。

例如:

// scripts/specifiedError.js
// Create a custom error
var SpecifiedError = function SpecifiedError(message) {
  this.name = 'SpecifiedError';
  this.message = message || '';
  this.stack = (new Error()).stack;
};
SpecifiedError.prototype = new Error();
SpecifiedError.prototype.constructor = SpecifiedError;
// scripts/uglyHandlerImproved.js
function uglyHandlerImproved(fn) {
  try {
    return fn();
  } catch (e) {
    throw new SpecifiedError(e.message);
  }
}
// tests/scripts/uglyHandlerImprovedTest.js
it('returns a specified error with errors', function () {
  var fn = function () {
    throw new TypeError('type error');
  };
  should.throws(function () {
    uglyHandlerImproved(fn);
  }, SpecifiedError);
});

指定的错误会添加更多详细信息并保留原始的错误消息。有了这个改进,以上的处理不再是较差的处理方式了,而是一个清晰有用的方式。

经过了上面的处理,我们还收到了一个未处理的异常。接下来让我们看看浏览器在处理错误时,有什么帮助。

 

展开堆栈

处理异常的一种方式是在调用堆栈的顶部加入try...catch。

比如说:

function main(bomb) {
  try {
    bomb();
  } catch (e) {
    // Handle all the error things
  }
}

但是,浏览器是事件驱动的, JavaScript中的异常也是一个事件。发生异常时,解释器会暂停执行并展开:

// scripts/errorHandlerDom.js
window.addEventListener('error', function (e) {
  var error = e.error;
  console.log(error);
});

此事件处理程序会捕获任何执行上下文中发生的错误。各个目标发生的错误事件会触发各种类型的错误。这种集中在代码中的错误处理是非常激进的。你可以使用菊花链处理方式来处理特定的错误。如果你遵循SOLID原则,就可以采用具有单一目的错误处理方式。这些处理程序可以随时进行注册,解释器会循环执行需要执行的处理程序。代码库可以从try...catch块中释放出来,这也使得调试变得容易。在JavaScript中,把错误处理当作事件处理很重要。

 

捕获堆栈

在解决问题时,调用堆栈会非常有用,同时浏览器正好可以提供这些信息。虽然堆栈属性不是标准的一部分,但是最新的浏览器已经可以查看这些信息了。

下面是在服务器上记录错误的示例:

// scripts/errorAjaxHandlerDom.js
window.addEventListener('error', function (e) {
  var stack = e.error.stack;
  var message = e.error.toString();
  if (stack) {
    message += '\n' + stack;
  }
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/log', true);
  // Fire an Ajax request with error details
  xhr.send(message);
});

每个错误处理都具有单个目的,这样可以保持代码的DRY原则(目的单一,不要重复自己原则)。

在浏览器中,需要将事件处理添加到DOM。这意味着如果你正在构建第三方库,那么你的事件会与客户端代码共存。window.addEventListener( )会帮你进行处理,同时也不会抹去现有的事件。

这是服务器上日志的截图:

可以通过命令提示符查看日志,但是Windows上,日志是非动态的。

通过日志可以清楚的看到,具体什么情况触发了什么错误。在调试时调用堆栈也会非常有用,所以不要低估调用堆栈的作用。

在JavaScript中,错误信息仅适用于单个域。因为在使用来自不用域的脚本时,将会看不到任何错误详细信息。

一种解决方案是重新抛出错误,同时保留错误消息:

try {
  return fn();
} catch (e) {
  throw new Error(e.message);
}

一旦重新启动了错误备份,全局错误处理程序就会完成其余的工作。确保你的错误处理处在相同域中,这样会保留原始消息,堆栈和自定义错误对象。

 

异步处理

JavaScript在运行异步代码时,进行下面的异常处理,会产生一个问题:

// scripts/asyncHandler.js
function asyncHandler(fn) {
  try {
    // This rips the potential bomb from the current context
    setTimeout(function () {
      fn();
    }, 1);
  } catch (e) { }
}

通过单元测试来查看问题:

// tests/scripts/asyncHandlerTest.js
it('does not catch exceptions with errors', function () {
  // The bomb
  var fn = function () {
    throw new TypeError('type error');
  };
  // Check that the exception is not caught
  should.doesNotThrow(function () {
    asyncHandler(fn);
  });
});

这个异常没有被捕获,我们通过单元测试来验证。尽管代码包含了try...catch,但是try...catch语句只能在单个执行上下文中工作。当异常被抛出时,解释器已经脱离了try...catch,所以异常未被处理。Ajax调用也会发生同样的情况。

所以,一种解决方案是在异步回调中捕获异常:

setTimeout(function () {
  try {
    fn();
  } catch (e) {
    // Handle this async error
  }
}, 1);

这种做法会比较奏效,但仍有很大的改进空间。

首先,这些try...catch block在整个区域纠缠不清。事实上,V8浏览器引擎不鼓励在函数内使用try ... catch block。V8是Chrome浏览器和Node中使用的JavaScript引擎。一种做法是将try...catch block移动到调用堆栈的顶部,但这却不适用于异步代码编程。

由于全局错误处理可以在任何上下文中执行,所以如果为错误处理添加一个窗口对象,那么就能保证代码的DRY和SOLID原则。同时全局错误处理也能保证你的异步代码很干净。

以下是该异常处理在服务器上的报告内容。请注意,输出内容会根据浏览器的不同而不同。

从错误处理中可以看到,错误来自于异步代码的setTimeout( )功能。

 

结论

在进行错误处理时,不要隐藏问题,而应该及时发现问题,并采用各种方法追溯问题的根源以便解决问题。虽然编写代码时,时常难免会埋下错误,但是我们也无须为错误的发生过于感到羞愧,及时解决发现问题从而避免更大的问题发生,正是我们现在需要做的。

 

JavaScript 开发工具介绍

SpreadJS 纯前端表格控件是基于 HTML5 的 JavaScript 电子表格和网格功能控件,提供了完备的公式引擎、排序、过滤、输入控件、数据可视化、Excel 导入/导出等功能,适用于 .NET、Java 和移动端等各平台在线编辑类 Excel 功能的表格程序开发。

原文链接:https://www.sitepoint.com/proper-error-handling-javascript/

转载请注明出自:葡萄城控件

 

关于葡萄城

葡萄城是全球控件行业领导者,世界领先的企业应用定制工具、企业报表和商业智能解决方案提供商,为超过75%的全球财富500强企业提供服务。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326434253&siteId=291194637