Window addeventlistener error

I would like to catch every undefined function error thrown. Is there a global error handling facility in JavaScript? The use case is catching function calls from flash that are not defined.

Try Atatus which provides Advanced Error Tracking and Real User Monitoring for modern web apps.

https://www.atatus.com/

Let me explain how to get stacktraces that are reasonably complete in all browsers.

Error handling in JavaScript

Modern Chrome and Opera fully support the HTML 5 draft spec for ErrorEvent and window.onerror. In both of these browsers you can either use window.onerror, or bind to the ‘error’ event properly:

// Only Chrome & Opera pass the error object.
window.onerror = function (message, file, line, col, error) {
    console.log(message, "from", error.stack);
    // You can send data to your server
    // sendError(data);
};
// Only Chrome & Opera have an error attribute on the event.
window.addEventListener("error", function (e) {
    console.log(e.error.message, "from", e.error.stack);
    // You can send data to your server
    // sendError(data);
})

Unfortunately Firefox, Safari and IE are still around and we have to support them too. As the stacktrace is not available in window.onerror we have to do a little bit more work.

It turns out that the only thing we can do to get stacktraces from errors is to wrap all of our code in a try{ }catch(e){ } block and then look at e.stack. We can make the process somewhat easier with a function called wrap that takes a function and returns a new function with good error handling.

function wrap(func) {
    // Ensure we only wrap the function once.
    if (!func._wrapped) {
        func._wrapped = function () {
            try{
                func.apply(this, arguments);
            } catch(e) {
                console.log(e.message, "from", e.stack);
                // You can send data to your server
                // sendError(data);
                throw e;
            }
        }
    }
    return func._wrapped;
};

This works. Any function that you wrap manually will have good error handling, but it turns out that we can actually do it for you automatically in most cases.

By changing the global definition of addEventListener so that it automatically wraps the callback we can automatically insert try{ }catch(e){ } around most code. This lets existing code continue to work, but adds high-quality exception tracking.

var addEventListener = window.EventTarget.prototype.addEventListener;
window.EventTarget.prototype.addEventListener = function (event, callback, bubble) {
    addEventListener.call(this, event, wrap(callback), bubble);
}

We also need to make sure that removeEventListener keeps working. At the moment it won’t because the argument to addEventListener is changed. Again we only need to fix this on the prototype object:

var removeEventListener = window.EventTarget.prototype.removeEventListener;
window.EventTarget.prototype.removeEventListener = function (event, callback, bubble) {
    removeEventListener.call(this, event, callback._wrapped || callback, bubble);
}

Transmit error data to your backend

You can send error data using image tag as follows

function sendError(data) {
    var img = newImage(),
        src = 'http://yourserver.com/jserror&data=' + encodeURIComponent(JSON.stringify(data));

    img.crossOrigin = 'anonymous';
    img.onload = function success() {
        console.log('success', data);
    };
    img.onerror = img.onabort = function failure() {
        console.error('failure', data);
    };
    img.src = src;
}

Disclaimer: I am a web developer at https://www.atatus.com/.

The error event is fired on a Window object when a resource failed to load or couldn’t be used — for example if a script has an execution error.

Syntax

Use the event name in methods like addEventListener(), or set an event handler property.

addEventListener("error", (event) => {});

onerror = (event, source, lineno, colno, error) => {};

Note: Due to historical reasons, onerror on window is the only event handler property that receives more than one argument.

Event type

The event object is a UIEvent instance if it was generated from a user interface element, or an Event instance otherwise.

Event

UIEvent

Usage notes

Unlike other events, the error event is canceled by returning true from the handler instead of returning false. When canceled, the error won’t appear in the console, but the current script will still stop executing.

The event handler’s signature is asymmetric between addEventListener() and onerror. The event handler passed to addEventListener receives a single ErrorEvent object, while the onerror handler receives five arguments, matching the ErrorEvent object’s properties:

event

A string containing a human-readable error message describing the problem. Same as ErrorEvent.message.

source

A string containing the URL of the script that generated the error.

lineno

An integer containing the line number of the script file on which the error occurred.

colno

An integer containing the column number of the script file on which the error occurred.

error

The error being thrown. Usually an Error object.

Note: These parameter names are observable with an HTML event handler attribute, where the first parameter is called event instead of message.

This special behavior only happens for the onerror event handler on window. The Element.onerror handler still receives a single ErrorEvent object.

Examples

Live example

HTML

<div class="controls">
  <button id="script-error" type="button">Generate script error</button>
  <img class="bad-img" />
</div>

<div class="event-log">
  <label for="eventLog">Event log:</label>
  <textarea
    readonly
    class="event-log-contents"
    rows="8"
    cols="30"
    id="eventLog"></textarea>
</div>
body {
  display: grid;
  grid-template-areas: "control log";
}

.controls {
  grid-area: control;
  display: flex;
  align-items: center;
  justify-content: center;
}

.event-log {
  grid-area: log;
}

.event-log-contents {
  resize: none;
}

label,
button {
  display: block;
}

button {
  height: 2rem;
  margin: 0.5rem;
}

img {
  width: 0;
  height: 0;
}

JavaScript

const log = document.querySelector(".event-log-contents");

window.addEventListener("error", (event) => {
  log.textContent = `${log.textContent}${event.type}: ${event.message}n`;
  console.log(event);
});

const scriptError = document.querySelector("#script-error");
scriptError.addEventListener("click", () => {
  const badCode = "const s;";
  eval(badCode);
});

Result

Specifications

Specification
HTML Standard
# event-error
HTML Standard
# handler-onerror

Browser compatibility

BCD tables only load in the browser

See also

  • This event on Element targets: error event

Try Atatus which provides Advanced Error Tracking and Real User Monitoring for modern web apps.

https://www.atatus.com/

Let me explain how to get stacktraces that are reasonably complete in all browsers.

Error handling in JavaScript

Modern Chrome and Opera fully support the HTML 5 draft spec for ErrorEvent and window.onerror. In both of these browsers you can either use window.onerror, or bind to the ‘error’ event properly:

// Only Chrome & Opera pass the error object.
window.onerror = function (message, file, line, col, error) {
    console.log(message, "from", error.stack);
    // You can send data to your server
    // sendError(data);
};
// Only Chrome & Opera have an error attribute on the event.
window.addEventListener("error", function (e) {
    console.log(e.error.message, "from", e.error.stack);
    // You can send data to your server
    // sendError(data);
})

Unfortunately Firefox, Safari and IE are still around and we have to support them too. As the stacktrace is not available in window.onerror we have to do a little bit more work.

It turns out that the only thing we can do to get stacktraces from errors is to wrap all of our code in a try{ }catch(e){ } block and then look at e.stack. We can make the process somewhat easier with a function called wrap that takes a function and returns a new function with good error handling.

function wrap(func) {
    // Ensure we only wrap the function once.
    if (!func._wrapped) {
        func._wrapped = function () {
            try{
                func.apply(this, arguments);
            } catch(e) {
                console.log(e.message, "from", e.stack);
                // You can send data to your server
                // sendError(data);
                throw e;
            }
        }
    }
    return func._wrapped;
};

This works. Any function that you wrap manually will have good error handling, but it turns out that we can actually do it for you automatically in most cases.

By changing the global definition of addEventListener so that it automatically wraps the callback we can automatically insert try{ }catch(e){ } around most code. This lets existing code continue to work, but adds high-quality exception tracking.

var addEventListener = window.EventTarget.prototype.addEventListener;
window.EventTarget.prototype.addEventListener = function (event, callback, bubble) {
    addEventListener.call(this, event, wrap(callback), bubble);
}

We also need to make sure that removeEventListener keeps working. At the moment it won’t because the argument to addEventListener is changed. Again we only need to fix this on the prototype object:

var removeEventListener = window.EventTarget.prototype.removeEventListener;
window.EventTarget.prototype.removeEventListener = function (event, callback, bubble) {
    removeEventListener.call(this, event, callback._wrapped || callback, bubble);
}

Transmit error data to your backend

You can send error data using image tag as follows

function sendError(data) {
    var img = newImage(),
        src = 'http://yourserver.com/jserror&data=' + encodeURIComponent(JSON.stringify(data));

    img.crossOrigin = 'anonymous';
    img.onload = function success() {
        console.log('success', data);
    };
    img.onerror = img.onabort = function failure() {
        console.error('failure', data);
    };
    img.src = src;
}

Disclaimer: I am a web developer at https://www.atatus.com/.

В этой статье мы научимся регистрировать ошибки скрипта с помощью JavaScript. Это полезно в случае, когда скрипты из любого другого источника запускаются / выполняются на веб-сайте (например, в iframe) или когда любой из ваших зрителей использует консоль браузера, пытаясь изменить скрипт, или вы хотите отслеживать свой JS-код для ошибки, которые могут возникнуть при запуске в производство.

Подход: мы изучим 3 метода для выполнения этой задачи — все они следуют одному и тому же подходу, но по-разному реализовать подход и извлечь полезную информацию. Используйте то, что лучше всего подходит для вашего случая.

  1. window.addEventListener («error», // yourErrorLoggerFunction ) — прикрепляет событие ошибки к объекту окна (DOM).
  2. document.querySelector (‘body’). onerror = // yourErrorLoggerFunction — Слушатель ошибок в теге body -> Работает как в одном источнике, так и в кросс-скриптах одного происхождения.
  3. Создайте элемент сценария и добавьте его к тегу body источника.

Все три метода могут быть реализованы как в одном источнике, так и в любом внешнем источнике.

Примечание. Убедитесь, что это первое действие, выполняемое браузером (в случае, если вы не уверены, что настоятельно рекомендуется использовать метод 2).

Метод 1. В этом методе мы присоединяем прослушиватель событий к нашему оконному объекту. Всякий раз, когда возникает какая-либо ошибка сценария, срабатывает прикрепленная функция errorLogger, и внутри errorLoggerFunction мы извлекаем всю полезную информацию из объекта события, который получает наша функция errorLogger.

window.addEventListener("error", errorLog);

function errorLog(event) {

    let msg = source = lineno = colno

                    = error = time = "";

    event.preventDefault();

    msg = event.message;

    source = event.filename;

    lineno = event.lineno;

    colno = event.colno;

    error = event.error;

    time = event.time;

}

Найдите HTML-код (метод 1):

HTML

<!DOCTYPE html>

< html lang = "en" >

< head >

< meta charset = "UTF-8" >

< meta name = "viewport" content =

"width=device-width, initial-scale=1.0" >

< title >Document</ title >

</ head >

< body >

< p style = "font-size:20px;color:rgb(219, 0, 0)" >

No Errors

</ p >

< script >

// Make Sure addEventListener is the

// First thing to be Executed

window.addEventListener("error", errorLog);

// String to Prepare Error Paragraph

let errorString = "";

const pElement = document.querySelector('p');

// This console.log creates Error

console.log(a)

function errorLog(event) {

let msg = source = lineno

= colno = error = time = "";

event.preventDefault();

msg = event.message;

source = event.filename;

lineno = event.lineno;

colno = event.colno;

error = event.error;

time = event.time;

errorString = `Script Error was Found< br >

Message-: ${msg}< br >Source Info-: ${source}>Source Info-: ${source}

< br >Line No-: ${lineno}< br >Coloumn No-:

${colno}< br >Error Info-: ${error}<>Time >Error Info-: ${error}< br >Time

at which Error Occured-: ${time}ms after

Page Load< br >< br >< br >< br >`;

pElement.innerHTML = errorString;

}

</ script >

</ body >

</ html >

Пример — МЕТОД 1

Метод 2: в этом методе мы будем использовать обработчик событий onerror.

  • Шаг 1. Получите тег тела этой HTML-разметки, в зависимости от вашего случая, либо внешнюю HTML-разметку, либо разметку текущей загружаемой страницы.
  • Шаг 2 — Теперь, когда у нас есть тег body в идентификаторе const, скажем, const bodyTag, мы добавим обработчик события onerror в bodyTag.

Javascript

const bodyTag =

bodyTag.onerror = errorLogger;

function errorLogger(msg, url,

lineNo, columnNo, error) {

}

Заметили ли вы разницу в функции регистратора двух вышеупомянутых методов? Обычно в случае срабатывания какого-либо события прикрепленная к нему функция получает объект события, но в этом случае (метод 2) мы получаем предварительно извлеченную информацию (для Подробная причина Обратитесь к MDN WebDocs).

Примечание. Помните о порядке получения информации об ошибках в регистраторе. Функция — Всего 5 информации — Последовательность всегда одинакова.

Найдите HTML-код (метод 2):

HTML

<!DOCTYPE html>

< html lang = "en" >

< head >

< meta charset = "UTF-8" >

< meta name = "viewport" content =

"width=device-width, initial-scale=1.0" >

</ head >

< body onerror="errorLogger(message,

source, lineno, colno, error)">

< p style="font-size:20px;

color:rgb(219, 0, 0)">

</ p >

< script >

// You can directly set the onerror

// Attribute or use below 2 lines

// Get body tag of External HTML

// Markup or of your current page

const bodyTag = document.querySelector('body');

// bodyTag.onerror = errorLogger;

let errorString = "";

const pElement = document.querySelector('p');

function errorLogger(msg, url,

lineNo, colNo, error) {

// Now process your Error Information

// as you desire

errorString += `Script Error was Found

< br >Message-: ${msg}< br >URL-: ${url}

< br >Line No-: ${lineNo}< br >Coloumn No-:

${colNo}< br >Error Info-: ${error}<> >Error Info-: ${error}< br >

< br >< br >< br >`;

pElement.innerHTML += errorString;

}

</ script >

</ body >

</ html >

Пример — МЕТОД 2

Метод 3: в этом методе мы прикрепим элемент сценария к веб-странице. Этот метод полезен в тех случаях, когда мы хотим, чтобы регистратор ошибок активировался в определенное время или в случаях выполнения внешнего скрипта и т. Д.

  • Шаг 1. Получите основной тег страницы разметки, на которую вы нацеливаетесь.
  • Шаг 2 — Подготовьте элемент скрипта и добавьте его.

Javascript

const htmlDOM =

let errScr = htmlDOM.document

.createElement( 'script' );

errScr.type = 'text/javascript' ;

errScr.innerText =

`window.addEventListener( "error" ,

errorLog); function errorLog(event)

{

htmlDOM.document.querySelector

( 'body' ).append(errScr);

Пример — МЕТОД 3

Объяснение примера — Метод 3: Как вы могли заметить, мы использовали iframe для доступа к index.html той же страницы (Same-Origin), мы поместили скрипт в parentPage для регистрации ошибок на другой веб-странице.

Предупреждение — хотя вы будете заблокированы, но остерегайтесь использования этих методов в случае ПЕРЕКРЕСТНОГО СКРИПТИРОВАНИЯ (CORS) (сценарии различного происхождения) без письменного разрешения владельца этой страницы.

Найдите HTML-код (метод 3):

HTML

<!DOCTYPE html>

< html lang = "en" >

< head >

< meta charset = "UTF-8" >

< meta name = "viewport" content =

"width=device-width, initial-scale=1.0" >

</ head >

< body >

< iframe src = "index.html"

frameborder = "0" >

</ iframe >

< script >

const htmlDOM = document

.querySelector('iframe').contentDocument;

let errScr = htmlDOM.createElement('script');

errScr.type = 'text/javascript';

errScr.innerText = `console.log("Script

Loaded");window.addEventListener("error",

errorLog);function errorLog(event){

console.log("Error can be Processed

Now....Use the eventObject as used

in method-1")}`;

htmlDOM.querySelector('body').append(errScr);

</ script >

</ body >

</ html >

Важная заметка —

  1. Вы можете использовать как innerText, так и innerHTML для написания js-скрипта, но мы рекомендуем использовать innerText, поскольку присутствие любого HTML-объекта в скрипте может создать любую нежелательную ошибку.
  2. В способе 3 цель и способ ее получения могут отличаться в разных случаях. Таким образом, код, используемый при реализации этого метода, может немного отличаться, но подход останется прежним.

The onerror property of the GlobalEventHandlers mixin is an event handler that processes error events.

Error events are fired at various targets for different kinds of errors:

  • When a JavaScript runtime error (including syntax errors and exceptions thrown within handlers) occurs, an error event using interface ErrorEvent is fired at window and window.onerror() is invoked (as well as handlers attached by EventTarget.addEventListener (not only capturing)).
  • When a resource (such as an <img> or <script>) fails to load, an error event using interface Event is fired at the element that initiated the load, and the onerror() handler on the element is invoked. These error events do not bubble up to window, but can be handled with a EventTarget.addEventListener configured with useCapture set to true.

Installing a global error event handler is useful for automated collection of error reports.

Syntax

For historical reasons, different arguments are passed to window.onerror and element.onerror handlers (as well as on error-type EventTarget.addEventListener handlers).

window.onerror

window.onerror = function(message, source, lineno, colno, error) {  };

Function parameters:

  • message: error message (string). Available as event (sic!) in HTML onerror="" handler.
  • source: URL of the script where the error was raised (string)
  • lineno: Line number where error was raised (number)
  • colno: Column number for the line where the error occurred (number)
  • error: Error Object (object). May be null if no corresponding Error Object is available.

When the function returns true, this prevents the firing of the default event handler.

window.addEventListener(‘error’)

window.addEventListener('error', function(event) {  })

event of type ErrorEvent contains all the information about the event and the error.

element.onerror

element.onerror = function(event) {  }

element.onerror accepts a function with a single argument of type Event.

A good example for this is when you are using an image tag, and need to specify a backup image in case the one you need is not available on the server for any reason.

<img src="imagefound.gif" onerror="this.onerror=null;this.src='imagenotfound.gif';" />

The reason we have the this.onerror=null in the function is that the browser will be stuck in an endless loop if the onerror image itself generates an error.

Notes

When an error occurs in a script, loaded from a different origin, the details of the error are not reported to prevent leaking information (see bug 363897). Instead the error reported is "Script error." This behavior can be overridden in some browsers using the crossorigin attribute on <script> and having the server send the appropriate CORS HTTP response headers. A workaround is to isolate «Script error.» and handle it knowing that the error detail is only viewable in the browser console and not accessible via JavaScript.

window.onerror = function (msg, url, lineNo, columnNo, error) {
  var string = msg.toLowerCase();
  var substring = 'script error';
  if (string.indexOf(substring) > -1){
    alert('Script Error: See Browser Console for Detail');
  } else {
    var message = [
      'Message: ' + msg,
      'URL: ' + url,
      'Line: ' + lineNo,
      'Column: ' + columnNo,
      'Error object: ' + JSON.stringify(error)
    ].join(' - ');

    alert(message);
  }

  return false;
};

When using the inline HTML markup (<body onerror="alert('an error occurred')">), the HTML specification requires arguments passed to onerror to be named event, source, lineno, colno, error. In browsers that have not implemented this requirement, they can still be obtained via arguments[0] through arguments[2].

Specifications

Browser compatibility

Desktop Mobile
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox for Android Opera Android Safari on IOS Samsung Internet
onerror

10

12

1

9

11.6

6

≤37

18

4

12

6

1.0

See also

To catch all JavaScript errors, we can use the window.onerror() method which acts like a global try-catch statement. The onerror event handler was the first feature to facilitate error handling in JavaScript. The error event is fired on the window object whenever an exception occurs on the page.

The onerror event handler provides three pieces of information to identify the exact nature of the error −

  • Error message − The same message that the browser would display for the given error

  • URL − The file in which the error occurred

  • Line number− The line number in the given URL that caused the error

Syntax

Followings are the syntax to catch all JavaScript errors using window.onerror method −

window.onerror = function (event, souce, lineno, colon, error) { }

OR

Window.addEventListener( "error" , ( ErrorEvent ) => { })

Here we can see both the syntaxes are doing the same thing but the in onerror receiving are five parameters while the addEventListener receiving one parameter which wraps up all the five properties of the onerror, these properties are following −

  • event − The human readable error message that describes the problem in form of a string.

  • source − The URL of the script file that generated the error. It is also in form of a string.

  • lineno − The line number of the script file that generated the error. It is in Integer format.

  • colon − The column number of the script file that generated the error. It is in Integer format.

  • error − This is the Error Object that being thrown.

Note: The window.onerror only recieves five parameters, when it comes to Element.onerror we receive only one ErrorEvent onbject.

Steps

To catch all JavaScript errors using the window.onerror event handler, follow these steps −

STEP 1 − Open your HTML file in a text editor

STEP 2 − Add an element in your HTML where you want to display the error messages. You can use a div element with an id attribute, like this:

STEP 3 − Add a script element in your HTML and define the window.onerror event handler function. This function will be called whenever an error occurs in your JavaScript code.

STEP 4 − You can use these arguments to display the error details in your HTML element.

STEP 5 − Add a button or any other element in your HTML that will trigger the error. You can use the onclick event handler to call a function that generates an error.

Example: Using the window.onerror() Method

In this example, we create an input tag and on clicking the input tag we call a function that doesn’t exist and by using the window.onerror we are printing the errors.

<html>
<head>
   <h2> Catching all JavaScript errors using window.onerror </h2>
</head>
<body>
   <p>Click the following to see the result:</p>
   <form>
      <input type="button" value="Click Me" onclick="myFunc();" />
   </form>
   <div id = "result"><div>
   <script>
      window.onerror = function (error, source, lineno, colno, error) {
         
         // Print the error message
         let output = document.getElementById("result");
         output.innerHTML += "Message : " + error + "<br>";
         
         // Print the url of the file that contains the error
         output.innerHTML += "Url : " + source + "<br>";
         
         // Print the line number from which the error generated
         output.innerHTML += "Line number : " + lineno + "<br>";
         
         // Print the column number of the error line
         output.innerHTML += "Column number : " + colno + "<br>";
         
         // Print he error object
         output.innerHTML += "Error Object : " + error;
      }
   </script>
</body>
</html>

Example 2: Using the window.adEventListener() Method

This is also same as the first example but we use the addEventListener instead of onerror.

<html>
<body>
   <h2> Catching all JavaScript errors using addEventListener </h2>
   <p>Click the following to see the result:</p>
   <form>
      <input type="button" value="Click Me" onclick="myFunc();" />
   </form>
   <div id = "result"></div>
   <script>
      num = 20;
      window.addEventListener("error", (ErrorEvent) => {
         let output = document.getElementById("result");
         
         // Print the error message
         output.innerHTML += "Message : " + ErrorEvent.message + "<br>";
         
         // Print the url of the file that contains the error
         output.innerHTML += "Url : " + ErrorEvent.filename + "<br>";
         
         // Print the line number from which the error generated
         output.innerHTML += "Line number : " + ErrorEvent.lineno + "<br>";
         
         // Print the column number of the error line
         output.innerHTML += "Column number : " + ErrorEvent.colno + "<br>";
         
         // Print he error object
         output.innerHTML += "Error Object : " + ErrorEvent.error;
      })
   </script>
 </body>
</html>

In summary, to catch all JavaScript errors, the window.onerror method or window.addEventListener method can be used. These methods act like a global try-catch statement and are triggered when an error occurs in the JavaScript code.

The onerror property of the GlobalEventHandlers mixin is an EventHandler that processes error events.

Error events are fired at various targets for different kinds of errors:

  • When a JavaScript runtime error (including syntax errors and exceptions thrown within handlers) occurs, an error event using interface ErrorEvent is fired at window and window.onerror() is invoked (as well as handlers attached by window.addEventListener (not only capturing)).
  • When a resource (such as an <img> or <script>) fails to load, an error event using interface Event is fired at the element that initiated the load, and the onerror() handler on the element is invoked. These error events do not bubble up to window, but can be handled with a window.addEventListener configured with useCapture set to true.

Installing a global error event handler is useful for automated collection of error reports.

Syntax

For historical reasons, different arguments are passed to window.onerror and element.onerror handlers (as well as on error-type window.addEventListener handlers).

window.onerror

window.onerror = function(message, source, lineno, colno, error) { ... };

Function parameters:

  • message: error message (string). Available as event (sic!) in HTML onerror="" handler.
  • source: URL of the script where the error was raised (string)
  • lineno: Line number where error was raised (number)
  • colno: Column number for the line where the error occurred (number)
  • error: Error Object (object)

When the function returns true, this prevents the firing of the default event handler.

window.addEventListener(‘error’)

window.addEventListener('error', function(event) { ... })

event of type ErrorEvent contains all the information about the event and the error.

element.onerror

element.onerror = function(event) { ... }

element.onerror accepts a function with a single argument of type Event.

A good example for this is when you are using an image tag, and need to specify a backup image in case the one you need is not available on the server for any reason.

<img src="imagenotfound.gif" onerror="this.onerror=null;this.src='imagefound.gif';" />

The reason we have the this.onerror=null in the function is that the browser will be stuck in an endless loop if the onerror image itself generates an error.

Notes

When an error occurs in a script, loaded from a different origin, the details of the error are not reported to prevent leaking information (see bug 363897). Instead the error reported is simply "Script error." This behavior can be overriden in some browsers using the crossorigin attribute on <script> and having the server send the appropriate CORS HTTP response headers.  A workaround is to isolate «Script error.» and handle it knowing that the error detail is only viewable in the browser console and not accessible via JavaScript.

window.onerror = function (msg, url, lineNo, columnNo, error) {
  var string = msg.toLowerCase();
  var substring = "script error";
  if (string.indexOf(substring) > -1){
    alert('Script Error: See Browser Console for Detail');
  } else {
    var message = [
      'Message: ' + msg,
      'URL: ' + url,
      'Line: ' + lineNo,
      'Column: ' + columnNo,
      'Error object: ' + JSON.stringify(error)
    ].join(' - ');

    alert(message);
  }

  return false;
};

When using the inline HTML markup (<body onerror="alert('an error occurred')">), the HTML specification requires arguments passed to onerror to be named event, source, lineno, colno, error. In browsers that have not implemented this requirement, they can still be obtained via arguments[0] through arguments[2].

Specifications

Browser compatibility

The compatibility table on this page is generated from structured data. If you’d like to contribute to the data, please check out https://github.com/mdn/browser-compat-data and send us a pull request.

Update compatibility data on GitHub

Desktop Mobile
Chrome Edge Firefox Internet Explorer Opera Safari Android webview Chrome for Android Firefox for Android Opera for Android Safari on iOS Samsung Internet
onerror Chrome

Full support
10

Edge

Full support
12

Firefox

Full support
1

IE

Full support
9

Opera

Full support
11.6

Safari

Full support
6

WebView Android

Full support
≤37

Chrome Android

Full support
18

Firefox Android

Full support
4

Opera Android

Full support
12

Safari iOS

Full support
6

Samsung Internet Android

Full support
1.0

Legend

Full support  
Full support

See also

  • Capture and report JavaScript errors with window.onerror (blog.sentry.io, 2016)
  • How to catch JavaScript Errors with window.onerror (even on Chrome and Firefox) (danlimerick.wordpress.com, 2014)

JavaScript Errors Handbook

This README contains information that I’ve learned over the years about dealing with JavaScript errors, reporting them to the server, and navigating through a lot of bugs that can make this all really hard. Browsers have improved in this area, but there is still room left to improve to make sure that all applications can sanely and soundly handle any error that happens.

Test cases for content found in this guide can be found at https://mknichel.github.io/javascript-errors/.

Table of Contents

  • Introduction
  • Anatomy of a JavaScript Error
    • Producing a JavaScript Error
    • Error Messages
    • Stack Trace Format
  • Catching JavaScript Errors
    • window.onerror
    • try/catch
    • Protected Entry Points
    • Promises
    • Web Workers
    • Chrome Extensions

Introduction

Catching, reporting, and fixing errors is an important part of any application to ensure the health and stability of the application. Since JavaScript code is also executed on the client and in many different browser environments, staying on top of JS Errors from your application can also be hard. There are no formal web specs for how to report JS errors which cause differences in each browser’s implementation. Additionally, there have been many bugs in browsers’ implementation of JavaScript errors as well that have made this even harder. This page navigates through these aspects of JS Errors so that future developers can handle errors better and browsers will hopefully converge on standardized solutions.

Anatomy of a JavaScript Error

A JavaScript error is composed of two primary pieces: the error message and the stack trace. The error message is a string that describes what went wrong, and the stack trace describes where in the code the error happened. JS Errors can be produced either by the browser itself or thrown by application code.

Producing a JavaScript Error

A JS Error can be thrown by the browser when a piece of code doesn’t execute properly, or it can be thrown directly by code.

For example:

In this example, a variable that is actually a number can’t be invoked as a function. The browser will throw an error like TypeError: a is not a function with a stack trace that points to that line of code.

A developer might also want to throw an error in a piece of code if a certain precondition is not met. For example

if (!checkPrecondition()) {
  throw new Error("Doesn't meet precondition!");
}

In this case, the error will be Error: Doesn't meet precondition!. This error will also contain a stack trace that points to the appropriate line. Errors thrown by the browser and application code can be handled the same.

There are multiple ways that developers can throw an error in JavaScript:

  • throw new Error('Problem description.')
  • throw Error('Problem description.') <— equivalent to the first one
  • throw 'Problem description.' <— bad
  • throw null <— even worse

Throwing a string or null is really not recommended since the browser will not attach a stack trace to that error, losing the context of where that error ocurred in the code. It is best to throw an actual Error object, which will contain the error message as well as a stack trace that points to the right lines of code where the error happened.

Error Messages

Each browser has its own set of messages that it uses for the built in exceptions, such as the example above for trying to call a non-function. Browsers will try to use the same messages, but since there is no spec, this is not guaranteed. For example, both Chrome and Firefox use {0} is not a function for the above example while IE11 will report Function expected (notably also without reporting what variable was attempted to be called).

However, browsers tend to diverge often as well. When there are multiple default statements in a switch statement, Chrome will throw "More than one default clause in switch statement" while Firefox will report "more than one switch default". As new features are added to the web, these error messages have to be updated. These differences can come into play later when you are trying to handle reported errors from obfuscated code.

You can find the templates that browsers use for error messages at:

  • Firefox — http://mxr.mozilla.org/mozilla1.9.1/source/js/src/js.msg
  • Chrome — https://code.google.com/p/v8/source/browse/branches/bleeding_edge/src/messages.js
  • Internet Explorer — https://github.com/Microsoft/ChakraCore/blob/4e4d4f00f11b2ded23d1885e85fc26fcc96555da/lib/Parser/rterrors.h

error message warning Browsers will produce different error messages for some exceptions.

Stack Trace Format

The stack trace is a description of where the error happened in the code. It is composed of a series of frames, where each frames describe a particular line in the code. The topmost frame is the location where the error was thrown, while the subsequent frames are the function call stack — or how the code was executed to get to that point where the error was thrown. Since JavaScript is usually concatenated and minified, it is also important to have column numbers so that the exact statement can be located when a given line has a multitude of statements.

A basic stack trace in Chrome looks like:

  at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9)
  at http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3

Each stack frame consists of a function name (if applicable and the code was not executed in the global scope), the script that it came from, and the line and column number of the code.

Unfortunately, there is no standard for the stack trace format so this differs by browser.

Microsoft Edge and IE 11’s stack trace looks similar to Chrome’s except it explicitly lists Global code:

  at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:3)
  at Global code (http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3)

Firefox’s stack trace looks like:

  throwError@http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9
  @http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3

Safari’s format is similar to Firefox’s format but is also slightly different:

  throwError@http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:18
  global code@http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:13

The same basic information is there, but the format is different.

Also note that in the Safari example, aside from the format being different than Chrome, the column numbers are different than both Chrome and Firefox. The column numbers also can deviate more in different error situations — for example in the code (function namedFunction() { throwError(); })();, Chrome will report the column for the throwError() function call while IE11 reports the column number as the start of the string. These differences will come back into play later when the server needs to parse the stack trace for reported errors and deobfuscate obfuscated stack traces.

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack for more information on the stack property of errors. When accessing the Error.stack property, Chrome does include the error message as part of the stack but Safari 10+ does not.

stack trace format warning The format of stack traces is different by browser in form and column numbers used.

Diving in more, there are a lot of nuances to stack trace formats that are discussed in the below sections.

Naming anonymous functions

By default, anonymous functions have no name and either appear as empty string or «Anonymous function» in the function names in the stack trace (depending on the browser). To improve debugging, you should add a name to all functions to ensure it appears in the stack frame. The easiest way to do this is to ensure that anonymous functions are specified with a name, even if that name is not used anywhere else. For example:

setTimeout(function nameOfTheAnonymousFunction() { ... }, 0);

This will cause the stack trace to go from:

at http://mknichel.github.io/javascript-errors/javascript-errors.js:125:17

to

at nameOfTheAnonymousFunction (http://mknichel.github.io/javascript-errors/javascript-errors.js:121:31)

In Safari, this would go from:

https://mknichel.github.io/javascript-errors/javascript-errors.js:175:27

to

nameOfTheAnonymousFunction@https://mknichel.github.io/javascript-errors/javascript-errors.js:171:41

This method ensures that nameOfTheAnonymousFunction appears in the frame for any code from inside that function, making debugging much easier. See http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/#toc-debugging-tips for more information.

Assigning functions to a variable

Browsers will also use the name of the variable or property that a function is assigned to if the function itself does not have a name. For example, in

var fnVariableName = function() { ... };

browsers will use fnVariableName as the name of the function in stack traces.

    at throwError (http://mknichel.github.io/javascript-errors/javascript-errors.js:27:9)
    at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37)

Even more nuanced than that, if this variable is defined within another function, all browsers will use just the name of the variable as the name of the function in the stack trace except for Firefox, which will use a different form that concatenates the name of the outer function with the name of the inner variable. Example:

function throwErrorFromInnerFunctionAssignedToVariable() {
  var fnVariableName = function() { throw new Error("foo"); };
  fnVariableName();
}

will produce in Firefox:

throwErrorFromInnerFunctionAssignedToVariable/fnVariableName@http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37

In other browsers, this would look like:

at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37)

inner function Firefox stack frame warning Firefox uses different stack frame text for functions defined within another function.

displayName Property

The display name of a function can also be set by the displayName property in all major browsers except for IE11. In these browsers, the displayName will appear in the devtools debugger, but in all browsers but Safari, it will not be used in Error stack traces (Safari differs from the rest by also using the displayName in the stack trace associated with an error).

var someFunction = function() {};
someFunction.displayName = " # A longer description of the function.";

There is no official spec for the displayName property, but it is supported by all the major browsers. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/displayName and http://www.alertdebugging.com/2009/04/29/building-a-better-javascript-profiler-with-webkit/ for more information on displayName.

IE11 no displayName property IE11 doesn’t support the displayName property.

Safari displayName property bug Safari uses the displayName property as the symbol name in Error stack traces.

Programatically capturing stack traces

If an error is reported without a stack trace (see more details when this would happen below), then it’s possible to programatically capture a stack trace.

In Chrome, this is really easy to do by using the Error.captureStackTrace API. See https://github.com/v8/v8/wiki/Stack%20Trace%20API for more information on the use of this API.

For example:

function ignoreThisFunctionInStackTrace() {
  var err = new Error();
  Error.captureStackTrace(err, ignoreThisFunctionInStackTrace);
  return err.stack;
}

In other browsers, a stack trace can also be collected by creating a new error and accessing the stack property of that object:

var err = new Error('');
return err.stack;

However, IE10 only populates the stack trace when the error is actually thrown:

try {
  throw new Error('');
} catch (e) {
  return e.stack;
}

If none of these approaches work, then it’s possible to create a rough stack trace without line numbers or columns by iterating over the arguments.callee.caller object — this won’t work in ES5 Strict Mode though and it’s not a recommended approach.

Async stack traces

It is very common for asynchronous points to be inserted into JavaScript code, such as when code uses setTimeout or through the use of Promises. These async entry points can cause problems for stack traces, since they cause a new execution context to form and the stack trace starts from scratch again.

Chrome DevTools has support for async stack traces, or in other words making sure the stack trace of an error also shows the frames that happened before the async point was introduced. With the use of setTimeout, this will capture who called the setTimeout function that eventually produced an error. See http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/ for more information.

An async stack trace will look like:

  throwError	@	throw-error.js:2
  setTimeout (async)		
  throwErrorAsync	@	throw-error.js:10
  (anonymous function)	@	throw-error-basic.html:14

Async stack traces are only supported in Chrome DevTools right now, only for exceptions that are thrown when DevTools are open. Stack traces accessed from Error objects in code will not have the async stack trace as part of it.

It is possible to polyfill async stack traces in some cases, but this could cause a significant performance hit for your application since capturing a stack trace is not cheap.

Only Chrome supports async stack traces Only Chrome DevTools natively supports async stack traces.

Naming inline scripts and eval

Stack traces for code that was eval’ed or inlined into a HTML page will use the page’s URL and line/column numbers for the executed code.

For example:

  at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9)
  at http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3

If these scripts actually come from a script that was inlined for optimization reasons, then the URL, line, and column numbers will be wrong. To work around this problem, Chrome and Firefox support the //# sourceURL= annotation (Safari, Edge, and IE do not). The URL specified in this annotation will be used as the URL for all stack traces, and the line and column number will be computed relative to the start of the <script> tag instead of the HTML document. For the same error as above, using the sourceURL annotation with a value of «inline.js» will produce a stack trace that looks like:

  at throwError (http://mknichel.github.io/javascript-errors/inline.js:8:9)
  at http://mknichel.github.io/javascript-errors/inline.js:12:3

This is a really handy technique to make sure that stack traces are still correct even when using inline scripts and eval.

http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl describes the sourceURL annotation in more detail.

Lack of sourceURL support Safari, Edge, and IE do not support the sourceURL annotation for naming inline scripts and evals. If you use inline scripts in IE or Safari and you obfuscate your code, you will not be able to deobfuscate errors that come from those scripts.

Chrome bug for computing line numbers with sourceURL Up until Chrome 42, Chrome did not compute line numbers correctly for inline scripts that use the sourceURL annotation. See https://bugs.chromium.org/p/v8/issues/detail?id=3920 for more information.

Chrome bug in line numbers from inline scripts Line numbers for stack frames from inline scripts are incorrect when the sourceURL annotation is used since they are relative to the start of the HTML document instead of the start of the inline script tag (making correct deobfuscation not possible). https://code.google.com/p/chromium/issues/detail?id=578269

Eval stack traces

For code that uses eval, there are other differences in the stack trace besides whether or not it uses the sourceURL annotation. In Chrome, a stack trace from a statement used in eval could look like:

Error: Error from eval
    at evaledFunction (eval at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3), <anonymous>:1:36)
    at eval (eval at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3), <anonymous>:1:68)
    at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3)

In MS Edge and IE11, this would look like:

Error from eval
    at evaledFunction (eval code:1:30)
    at eval code (eval code:1:2)
    at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3)

In Safari:

Error from eval
    evaledFunction
    eval code
    eval@[native code]
    evalError@http://mknichel.github.io/javascript-errors/javascript-errors.js:137:7

and in Firefox:

Error from eval
    evaledFunction@http://mknichel.github.io/javascript-errors/javascript-errors.js line 137 > eval:1:36
    @http://mknichel.github.io/javascript-errors/javascript-errors.js line 137 > eval:1:11
    evalError@http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3

These differences can make it hard to parse eval code the same across all browsers.

Different eval stack trace format across browsers Each browser uses a different stack trace format for errors that happened inside eval.

Stack traces with native frames

Your JavaScript code can also be called directly from native code. Array.prototype.forEach is a good example — you pass a function to forEach and the JS engine will call that function for you.

function throwErrorWithNativeFrame() {
  var arr = [0, 1, 2, 3];
  arr.forEach(function namedFn(value) {
    throwError();
  });
}

This produces different stack traces in different browsers. Chrome and Safari append the name of the native function in the stack trace itself as a separate frame, such as:

(Chrome)
    at namedFn (http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5)
    at Array.forEach (native)
    at throwErrorWithNativeFrame (http://mknichel.github.io/javascript-errors/javascript-errors.js:152:7)

(Safari)
    namedFn@http://mknichel.github.io/javascript-errors/javascript-errors.js:153:15
    forEach@[native code]
    throwErrorWithNativeFrame@http://mknichel.github.io/javascript-errors/javascript-errors.js:152:14

(Edge)
    at namedFn (http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5)
    at Array.prototype.forEach (native code)
    at throwErrorWithNativeFrame (http://mknichel.github.io/javascript-errors/javascript-errors.js:152:7)

However, Firefox and IE11 do not show that forEach was called as part of the stack:

namedFn@http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5
throwErrorWithNativeFrame@http://mknichel.github.io/javascript-errors/javascript-errors.js:152:3

Different native function stack frame behavior Some browsers include native code frames in stack traces, while others do not.

Catching JavaScript Errors

To detect that your application had an error, some code must be able to catch that error and report about it. There are multiple techniques for catching errors, each with their pros and cons.

window.onerror

window.onerror is one of the easiest and best ways to get started catching errors. By assigning window.onerror to a function, any error that is uncaught by another part of the application will be reported to this function, along with some information about the error. For example:

window.onerror = function(msg, url, line, col, err) {
  console.log('Application encountered an error: ' + msg);
  console.log('Stack trace: ' + err.stack);
}

https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror describes this in more detail.

Historically, there have been a few problems with this approach:

No Error object provided

The 5th argument to the window.onerror function is supposed to be an Error object. This was added to the WHATWG spec in 2013: https://html.spec.whatwg.org/multipage/webappapis.html#errorevent. Chrome, Firefox, and IE11 now properly provide an Error object (along with the critical stack property), but Safari, MS Edge, and IE10 do not. This works in Firefox since Firefox 14 (https://bugzilla.mozilla.org/show_bug.cgi?id=355430) and in Chrome since late 2013 (https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror, https://code.google.com/p/chromium/issues/detail?id=147127). Safari 10 launched support for the Error object in window.onerror.

Lack of support for Error in window.onerror Safari (versions below 10), MS Edge, and IE10 do not support an Error object with a stack trace in window.onerror.

Cross domain sanitization

In Chrome, errors that come from another domain in the window.onerror handler will be sanitized to «Script error.», «», 0. This is generally okay if you really don’t want to process the error if it comes from a script that you don’t care about, so the application can filter out errors that look like this. However, this does not happen in Firefox or Safari or IE11, nor does Chrome do this for try/catch blocks that wrap the offending code.

If you would like to receive errors in window.onerror in Chrome with full fidelity from cross domain scripts, those resources must provide the appropriate cross origin headers. See https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror for more information.

Cross domain sanitization in window.onerror Chrome is the only browser that will sanitize errors that come from another origin. Take care to filter these out, or set the appropriate headers.

Chrome Extensions

In old versions of Chrome, Chrome extensions that are installed on a user’s machine could also throw errors that get reported to window.onerror. This has been fixed in newer versions of Chrome. See the dedicated Chrome Extensions section below.

window.addEventListener(«error»)

The window.addEventListener("error") API works the same as the window.onerror API. See http://www.w3.org/html/wg/drafts/html/master/webappapis.html#runtime-script-errors for more information on this approach.

Showing errors in DevTools console for development

Catching errors via window.onerror does not prevent that error from also appearing in the DevTools console. This is most likely the right behavior for development since the developer can easily see the error. If you don’t want these errors to show up in production to end users, e.preventDefault() can be called if using the window.addEventListener approach.

Recommendation

window.onerror is the best tool to catch and report JS errors. It’s recommended that only JS errors with valid Error objects and stack traces are reported back to the server, otherwise the errors may be hard to investigate or you may get a lot of spam from Chrome extensions or cross domain scripts.

try/catch

Given the above section, unfortunately it’s not possible to rely on window.onerror in all browsers to capture all error information. For catching exceptions locally, a try/catch block is the obvious choice. It’s also possible to wrap entire JavaScript files in a try/catch block to capture error information that can’t be caught with window.onerror. This improves the situations for browsers that don’t support window.onerror, but also has some downsides.

Doesn’t catch all errors

A try/catch block won’t capture all errors in a program, such as errors that are thrown from an async block of code through window.setTimeout. Try/catch can be used with Protected Entry Points to help fill in the gaps.

Use protected entry points with try/catch try/catch blocks wrapping the entire application aren’t sufficient to catch all errors.

Deoptimizations

Old versions of V8 (and potentially other JS engines), functions that contain a try/catch block won’t be optimized by the compiler (http://www.html5rocks.com/en/tutorials/speed/v8/). Chrome fixed this in TurboFan (https://codereview.chromium.org/1996373002).

Protected Entry Points

An «entry point» into JavaScript is any browser API that can start execution of your code. Examples include setTimeout, setInterval, event listeners, XHR, web sockets, or promises. Errors that are thrown from these entry points will be caught by window.onerror, but in the browsers that don’t support the full Error object in window.onerror, an alternative mechanism is needed to catch these errors since the try/catch method mentioned above won’t catch them either.

Thankfully, JavaScript allows these entry points to be wrapped so that a try/catch block can be inserted before the function is invoked to catch any errors thrown by the code.

Each entry point will need slightly different code to protect the entry point, but the gist of the methodology is:

function protectEntryPoint(fn) {
  return function protectedFn() {
    try {
      return fn();
    } catch (e) {
      // Handle error.
    }
  }
}
_oldSetTimeout = window.setTimeout;
window.setTimeout = function protectedSetTimeout(fn, time) {
  return _oldSetTimeout.call(window, protectEntryPoint(fn), time);
};

Promises

Sadly, it’s easy for errors that happen in Promises to go unobserved and unreported. Errors that happen in a Promise but are not handled by attaching a rejection handler are not reported anywhere else — they do not get reported to window.onerror. Even if a Promise attaches a rejection handler, that code itself must manually report those errors for them to be logged. See http://www.html5rocks.com/en/tutorials/es6/promises/#toc-error-handling for more information. For example:

window.onerror = function(...) {
  // This will never be invoked by Promise code.
};

var p = new Promise(...);
p.then(function() {
  throw new Error("This error will be not handled anywhere.");
});

var p2 = new Promise(...);
p2.then(function() {
  throw new Error("This error will be handled in the chain.");
}).catch(function(error) {
  // Show error message to user
  // This code should manually report the error for it to be logged on the server, if applicable.
});

One approach to capture more information is to use Protected Entry Points to wrap invocations of Promise methods with a try/catch to report errors. This might look like:

  var _oldPromiseThen = Promise.prototype.then;
  Promise.prototype.then = function protectedThen(callback, errorHandler) {
    return _oldPromiseThen.call(this, protectEntryPoint(callback), protectEntryPoint(errorHandler));
  };

Errors in Promises will go unhandled by default Sadly, errors from Promises will go unhandled by default.

Error handling in Promise polyfills

Promise implementations, such as Q, Bluebird, and Closure handle errors in different ways which are better than the error handling in the browser implementation of Promises.

  • In Q, you can «end» the Promise chain by calling .done() which will make sure that if an error wasn’t handled in the chain, it will get rethrown and reported. See https://github.com/kriskowal/q#handling-errors
  • In Bluebird, unhandled rejections are logged and reported immediately. See http://bluebirdjs.com/docs/features.html#surfacing-unhandled-errors
  • In Closure’s goog.Promise implementation, unhandled rejections are logged and reported if no chain in the Promise handles the rejection within a configurable time interval (in order to allow code later in the program to add a rejection handler).

Long stack traces

The async stack trace section above discusses that browsers don’t capture stack information when there is an async hook, such as calling Promise.prototype.then. Promise polyfills feature a way to capture the async stack trace points which can make diagnosing errors much easier. This approach is expensive, but it can be really useful for capturing more debug information.

  • In Q, call Q.longStackSupport = true;. See https://github.com/kriskowal/q#long-stack-traces
  • In Bluebird, call Promise.longStackTraces() somewhere in the application. See http://bluebirdjs.com/docs/features.html#long-stack-traces.
  • In Closure, set goog.Promise.LONG_STACK_TRACES to true.

Promise Rejection Events

Chrome 49 added support for events that are dispatched when a Promise is rejected. This allows applications to hook into Promise errors to ensure that they get centrally reported along with the rest of the errors.

window.addEventListener('unhandledrejection', event => {
  // event.reason contains the rejection reason. When an Error is thrown, this is the Error object.
});

See https://googlechrome.github.io/samples/promise-rejection-events/ and https://www.chromestatus.com/feature/4805872211460096 for more information.

This is not supported in any other browser.

Web Workers

Web workers, including dedicated workers, shared workers, and service workers, are becoming more popular in applications today. Since all of these workers are separate scripts from the main page, they each need their own error handling code. It is recommended that each worker script install its own error handling and reporting code for maximum effectiveness handling errors from workers.

Dedicated workers

Dedicated web workers execute in a different execution context than the main page, so errors from workers aren’t caught by the above mechanisms. Additional steps need to be taken to capture errors from workers on the page.

When a worker is created, the onerror property can be set on the new worker:

var worker = new Worker('worker.js');
worker.onerror = function(errorEvent) { ... };

This is defined in https://html.spec.whatwg.org/multipage/workers.html#handler-abstractworker-onerror. The onerror function on the worker has a different signature than the window.onerror discussed above. Instead of accepting 5 arguments, worker.onerror takes a single argument: an ErrorEvent object. The API for this object can be found at https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent. It contains the message, filename, line, and column, but no stable browser today contains the «Error» object that contains the stack trace (errorEvent.error is null). Since this API is executed in the parent page’s scope, it would be useful for using the same reporting mechanism as the parent page; unfortunately due to the lack of a stack trace, this API is of limited use.

Inside of the JS run by the worker, you can also define an onerror API that follows the usual window.onerror API: https://html.spec.whatwg.org/multipage/webappapis.html#onerroreventhandler. In the worker code:

self.onerror = function(message, filename, line, col, error) { ... };

The discussion of this API mostly follows the discussion above for window.onerror. However, there are 2 notable things to point out:

Firefox and Safari do not report the «error» object as the 5th argument to the function, so these browsers do not get a stack trace from the worker (Chrome, MS Edge, and IE11 do get a stack trace). Protected Entry Points for the onmessage function within the worker can be used to capture stack trace information for these browsers.

Since this code executes within the worker, the code must choose how to report the error back to the server: It must either use postMessage to communicate the error back to the parent page, or install an XHR error reporting mechanism (discussed more below) in the worker itself.

In Firefox, Safari, and IE11 (but not in Chrome), the parent page’s window.onerror function will also be called after the worker’s own onerror and the onerror event listener set by the page has been called. However, this window.onerror will also not contain an error object and therefore won’t have a stack trace also. These browsers must also take care to not report errors from workers multiple times.

Shared workers

Chrome and Firefox support the SharedWorker API for sharing a worker among multiple pages. Since the worker is shared, it is not attached to one parent page exclusively; this leads to some differences in how errors are handled, although SharedWorker mostly follows the same information as the dedicated web worker.

In Chrome, when there is an error in a SharedWorker, only the worker’s own error handling within the worker code itself will be called (like if they set self.onerror). The parent page’s window.onerror will not be called, and Chrome does not support the inherited AbstractWorker.onerror that can be called in the parent page as defined in the spec.

In Firefox, this behavior is different. An error in the shared worker will cause the parent page’s window.onerror to be called, but the error object will be null. Additionally, Firefox does support the AbstractWorker.onerror property, so the parent page can attach an error handler of its own to the worker. However, when this error handler is called, the error object will be null so there will be no stack trace, so it’s of limited use.

Error handling for shared workers differs by browser.

Service Workers

Service Workers are a brand new spec that is currently only available in recent Chrome and Firefox versions. These workers follow the same discussion as dedicated web workers.

Service workers are installed by calling the navigator.serviceWorker.register function. This function returns a Promise which will be rejected if there was an error installing the service worker, such as it throwing an error during initialization. This error will only contain a string message and nothing else. Additionally, since Promises don’t report errors to window.onerror handlers, the application itself would have to add a catch block to the Promise to catch the error.

navigator.serviceWorker.register('service-worker-installation-error.js').catch(function(error) {
  // error typeof string
});

Just like the other workers, service workers can set a self.onerror function within the service workers to catch errors. Installation errors in the service worker will be reported to the onerror function, but unfortunately they won’t contain an error object or stack trace.

The service worker API contains an onerror property inherited from the AbstractWorker interface, but Chrome does not do anything with this property.

Worker Try/Catch

To capture stack traces in Firefox + Safari within a worker, the onmessage function can be wrapped in a try/catch block to catch any errors that propagate to the top.

self.onmessage = function(event) {
  try {
    // logic here
  } catch (e) {
    // Report exception.
  }
};

The normal try/catch mechanism will capture stack traces for these errors, producing an exception that looks like:

Error from worker
throwError@http://mknichel.github.io/javascript-errors/worker.js:4:9
throwErrorWrapper@http://mknichel.github.io/javascript-errors/worker.js:8:3
self.onmessage@http://mknichel.github.io/javascript-errors/worker.js:14:7

Chrome Extensions

Chrome Extensions deserve their own section since errors in these scripts can operate slightly differently, and historically (but not anymore) errors from Chrome Extensions have also been a problem for large popular sites.

Content Scripts

Content scripts are scripts that run in the context of web pages that a user visits. These scripts run in an isolated execution environment so they can access the DOM but they can not access JavaScript on the parent page (and vice versa).

Since content scripts have their own execution environment, they can assign to the window.onerror handler in their own script and it won’t affect the parent page. However, errors caught by window.onerror in the content script are sanitized by Chrome resulting in a «Script error.» with null filename and 0 for line and column. This bug is tracked by https://code.google.com/p/chromium/issues/detail?id=457785. Until that bug is fixed, a try/catch block or protected entry points are the only ways to catch JS errors in a content script with stack traces.

In years past, errors from content scripts would be reported to the window.onerror handler of the parent page which could result in a large amount of spammy error reports for popular sites. This was fixed in late 2013 though (https://code.google.com/p/chromium/issues/detail?id=225513).

Errors in Chrome Extensions are sanitized before being handled by window.onerror.

Browser Actions

Chrome extensions can also generate browser action popups, which are small HTML pages that spawn when a user clicks a Chrome extension icon to the right of the URL bar. These pages can also run JavaScript, in an entirely different execution environment from everything else. window.onerror works properly for this JavaScript.

Reporting Errors to the Server

Once the client is configured to properly catch exceptions with correct stack traces, these exceptions should be reported back to the server so they can be tracked, analyzed, and then fixed. Typically this is done with a XHR endpoint that records the error message and the stack trace information, along with any relevant client context information, such as the version of the code that’s running, the user agent, the user’s locale, and the top level URL of the page.

If the application uses multiple mechanisms to catch errors, it’s important to not report the same error twice. Errors that contain a stack trace should be preferred; errors reported without a stack trace can be hard to track down in a large application.

May 10, 2021

When writing code, we can expect a wide range of errors to happen. We should handle it by writing our code with that in mind. Even though that’s the case, some errors might slip through the cracks. This article looks into what happens with unhandled errors both in Node.js and in the browser.

Errors in JavaScript

In web development, many situations can result in an error. In general, we could divide them into two categories.

Programmer errors

Programmer errors are caused by bugs in our code. A common example is trying to access a property of undefined.

const response = undefined;

console.log(response.data);

Uncaught TypeError: Cannot read property ‘data’ of undefined

Operational errors

We might run into operational errors even if we wrote our code correctly. They don’t represent bugs but signify that something went wrong. A good example is a user sending a request when logging in, but providing a wrong password.

If you want to learn about the very basics of managing errors, check out Handling errors in JavaScript with try…catch and finally

What happens when an exception is thrown

In JavaScript, an error is an object that we can throw to indicate that something went wrong.

class PostNotFoundException extends Error {

  constructor(postId) {

    super(`Post with id ${postId} not found`);

  }

}

async getPostById(id) {

  const post = await this.postsRepository.findOne(id);

  if (post) {

    return post;

  }

  throw new PostNotFoundException(id);

}

The above snippet is based on the code from the API with NestJS series.

A crucial thing to understand is how JavaScript reacts to exceptions being thrown. First, it stops the execution of the code. Then, it moves up through the call stack of our functions, looking for the closest
try...catch block.

function divide(firstNumber, secondNumber) {

  if (secondNumber === 0) {

    throw Error(«You can’t divide by zero»);

  }

  return firstNumber / secondNumber;

}

try {

  divide(5, 0);

  console.log(‘This console.log will not run’);

} catch(exception) {

  console.log(‘An exception has been caught’);

}

Above, because the
divide function threw an error, we can see
An exception has been caught in the console.

Let’s look into the MDN documentation for the throw statement:

If no
catch block exists among caller functions, the program will terminate.

There is quite a bit more to it, though. When there is an uncaught error in the browser, it aborts processing the current script or a file. When this happens, the
window object emits the error event. We can listen to it with
addEventListener.

window.addEventListener(‘error’, () => {

  console.log(‘An uncaught error happened’);

});

A legacy way to listen for the error event is to use
window.onerror.

If we attempt to try the above code out, we might run into an issue. The above event does not fire for errors originated from the Developer Tools console. This applies both to Chrome and Firefox.

Listening to the error event allows us to create a last-resort error handling. For example, we could implement a logic that redirects the user to a page that informs that an unexpected error happened.

React embraces this approach with error boundaries. If you want to know more, check out Bug-proofing our application with Error Boundaries and the Strict Mode

We can access the error event in the listener. Aside from accessing its properties, we can call the
preventDefault() method. Doing so causes the error not to be seen in the console.

window.addEventListener(‘error’, (event) => {

  // An uncaught error happened. It will not be logged in the console.

  event.preventDefault();

});

If we throw an error in the above listener, it does not emit the error event. Thanks to that, the JavaScript engine avoids infinite recursion.

Uncaught errors in promises

We should also implement error handling for our promises.

fetch(‘https://site-does-not-exist.js’);

When we don’t catch the above promise rejection, we can expect the error to appear in the console.

Uncaught (in promise) TypeError: Failed to fetch

When this happens, the
window object emits the
unhandledrejection event. We can access the PromiseRejectionEvent and prevent the error from appearing in the console.

window.addEventListener(‘unhandledrejection’, (event) => {

  // preventing the rejection from appearing in the console

  event.preventDefault();

});

A legacy way of attach a listener to this event is to use
window.onunhandledrejection.

There are some edge cases that we should know about. Let’s consider the code below:

const request = fetch(‘https://site-does-not-exist.js’);

request.then(() => console.log(‘Resource fetched’));

request.catch(() => console.log(‘Something went wrong’));

Something went wrong
Uncaught (in promise) TypeError: Failed to fetch

Above, we can see that error appears to be uncaught even though we’ve used the
catch function. To understand this, let’s look closer into the then function. It accepts two arguments: a function called when the promise resolves, and the callback used when the promise is rejected. If we don’t explicitly provide the rejection callback, JavaScript does the following:

const request = fetch(‘https://site-does-not-exist.js’);

request.then(

  () => console.log(‘Resource fetched’),

  (error) => {

    throw error;

  }

);

request.catch(() => console.log(‘Something went wrong’));

We don’t catch the error thrown in the
then callback, and therefore, it appears as uncaught in the console. This issue does not appear if we chain the
then and
catch functions.

const request = fetch(‘https://site-does-not-exist.js’);

request

  .then(() => console.log(‘Resource fetched’))

  .catch(() => console.log(‘Something went wrong’));

Uncaught errors in Node.js

By default, causing an uncaught exception in Node.js results in printing the stack trace and exiting the process with code
1. When a process returns a non-zero code, it signifies an error.

throw new Error(‘Oops!’);

/home/marcin/Documents/Projects/errors/index.js:1

throw new Error(‘Oops!’);

^

Error: Oops!

    at Object.<anonymous> (/home/marcin/Documents/Projects/errors/index.js:1:7)

    at Module._compile (internal/modules/cjs/loader.js:1137:30)

    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1157:10)

    at Module.load (internal/modules/cjs/loader.js:985:32)

    at Function.Module._load (internal/modules/cjs/loader.js:878:14)

    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)

    at internal/main/run_main_module.js:17:47

With Node.js, we don’t have the
window object. Even though that’s the case, there are some events related to uncaught exceptions.

By listening to the
uncaughtException error, we can alter the default behavior. When we do that, Node.js no longer returns code
1 but still terminates the process. We can restore the exit code through the
process.exitCode property.

If we want to listen for uncaught exceptions, but we don’t want to alter the default behavior, we can listen to the
uncaughtExceptionMonitor event instead.

process.on(‘uncaughtException’, (error) => {

  console.log(‘Uncaught exception happened’, error);

  process.exitCode = 1;

});

throw new Error(‘Oops!’);

We can also use
process.setUncaughtExceptionCaptureCallback to attach the above listener.

Although we could restart the process within the above listener, Node.js documentation strongly advises against it. The
uncaughtException listener is a place to perform the synchronous cleanup of allocated resources, such as files. It is not recommended to resume operation after the
uncaughtException event was emitted.

If we want to restart a crashed application more reliably, the official documentation suggests using an external monitor. An example of such is PM2.

Uncaught errors in promises

The topic of unhandled promise rejections in Node.js is somewhat tricky. This is because the default behavior changed across the Node.js versions.

const fs = require(‘fs’);

const util = require(‘util’);

const readFile = util.promisify(fs.readFile);

readFile(‘./non-existent-file.txt’)

  .then((content) => console.log(content));

Above, I use the
promisify function to work with promises instead of callbacks. If you want to know more about the file system module, check out Node.js TypeScript #1. Modules, process arguments, basics of the File System

When running the above code with Node.js version previous to 15, we can see the following:

UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag --unhandled-rejections=strict (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
[DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

This means that the process is not terminated. This behavior has been changed in Node.js 15, and the
unhandledrejections flag is now set to
throw by default. Because of that, running into an unhandled promise rejection terminates the process if we don’t listen to the
unhandledRejection event.

We can still set
unhandledrejections to
warn if we need the previous behavior.

const fs = require(‘fs’);

const util = require(‘util’);

const readFile = util.promisify(fs.readFile);

process.on(‘unhandledRejection’, (reason, promise) => {

  console.log(‘Unhandled Rejection at:’, promise, ‘reason:’, reason);

});

readFile(‘./non-existent-file.txt’);

Node.js emits the
unhandledRejection event every time a promise is rejected without error handling. By listening to it, we can keep track of rejected promises and implement logging, for example.

If we set
unhandledrejections to
strict, our process terminates on uncaught promise rejections even if we listen to the
unhandledRejection event.

Summary

In this article, we’ve looked into uncaught errors in JavaScript. It turns out, the browsers and Node.js handle it differently. A topic worth looking into more was unhandled promise rejections. The behavior differs not only between browsers and Node.js but also across different Node.js versions. All this knowledge can come in handy when debugging and building logging functionalities.

Понравилась статья? Поделить с друзьями:
  • Windload exe ошибка
  • Wincor1 ru ошибки
  • Wincc ошибка резервирования
  • Wincc runtime error
  • Winbrand dll как исправить