Механізм глобальних подій JavaScript


350

Я хотів би зрозуміти кожну викинуту невизначену функцію. Чи є у JavaScript JavaScript засобом обробки помилок? Випадок використання - це виклик функцій із спалаху, які не визначені.


Що ви хочете зробити з помилкою, як тільки ви її виявите? Вам просто потрібно ввійти в систему, щоб ви могли створити функцію, яка відсутня, або ви хочете зупинити винятки з порушення коду?
Ден Герберт

2
Я хотів би отримати ім'я відсутньої функції, яка називається, і заснована на наявності якогось рядкового виклику моєї власної функції. Будь-який виклик функції із рядком 'close', наприклад, міг би викликати мій close () Я також хотів би захопити помилку в цій точці.

2
exceptionsjs.com надає цю функціональність і може бути розроблений лише для того, щоб вловлювати помилки, пов'язані з невизначеною функціональністю, з її функцією "guard".
Стівен Векслер

Відповіді:


192

Чи допомагає це вам:

<script type="text/javascript">
window.onerror = function() {
    alert("Error caught");
};

xxx();
</script>

Я не впевнений, як він обробляє помилки Flash, хоча ...

Оновлення: він не працює в Opera, але я зараз зламаю Dragonfly, щоб побачити, що він отримує. Пропозиція щодо злому Dragonfly виникла з цього питання:

Мімічне вікно. onerror в Opera за допомогою JavaScript


3
З додаванням msg, file_loc, line_no парами це повинно зробити це для мене. Дякую!

1
Чи правильно я розумію, що цей код перекриває будь-які наявні обробники помилок?
Марс Робертсон

@MarsRobertson No.
atilkan

@atilkan window.onerror = function() { alert(42) };тепер код у відповіді: window.onerror = function() { alert("Error caught"); };не перекрито, я все ще не впевнений ..
Марс Робертсон

@MarsRobertson Я розумію. Це, ймовірно, перезаписується. Так, це, просто перевірено. Використання addEventListener було б краще.
atilkan

646

Як ловити необроблені помилки Javascript

Призначте window.onerrorподію обробнику події, наприклад:

<script type="text/javascript">
window.onerror = function(msg, url, line, col, error) {
   // Note that col & error are new to the HTML 5 spec and may not be 
   // supported in every browser.  It worked for me in Chrome.
   var extra = !col ? '' : '\ncolumn: ' + col;
   extra += !error ? '' : '\nerror: ' + error;

   // You can view the information in an alert to see things working like this:
   alert("Error: " + msg + "\nurl: " + url + "\nline: " + line + extra);

   // TODO: Report this error via ajax so you can keep track
   //       of what pages have JS issues

   var suppressErrorAlert = true;
   // If you return true, then error alerts (like in older versions of 
   // Internet Explorer) will be suppressed.
   return suppressErrorAlert;
};
</script>

Як коментується в коді, якщо значення повернення window.onerrorдорівнюєtrue тоді браузер повинен придушити показ діалогового вікна попередження.

Коли запускається подія window.onerror?

Коротше кажучи, подія піднімається, коли будь-який 1.) має місце виняткове вилучення, або 2.) виникає помилка часу компіляції.

невловимі винятками

  • кинути "деякі повідомлення"
  • call_something_undefined ();
  • cross_origin_iframe.contentWindow.document;, виняток безпеки

помилка компіляції

  • <script>{</script>
  • <script>for(;)</script>
  • <script>"oops</script>
  • setTimeout("{", 10);, він спробує скомпілювати перший аргумент як сценарій

Браузери, що підтримують window.onerror

  • Chrome 13+
  • Firefox 6.0+
  • Internet Explorer 5.5+
  • Опера 11.60+
  • Сафарі 5.1+

Знімок екрана:

Приклад коду onerror, що знаходиться вище, після додавання цього на тестову сторінку:

<script type="text/javascript">
call_something_undefined();
</script>

Попередження Javascript, що показує інформацію про помилку, детальну інформацію про подію window.onerror

Приклад звітування про помилки AJAX

var error_data = {
    url: document.location.href,
};

if(error != null) {
    error_data['name'] = error.name; // e.g. ReferenceError
    error_data['message'] = error.line;
    error_data['stack'] = error.stack;
} else {
    error_data['msg'] = msg;
    error_data['filename'] = filename;
    error_data['line'] = line;
    error_data['col'] = col;
}

var xhr = new XMLHttpRequest();

xhr.open('POST', '/ajax/log_javascript_error');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
    if (xhr.status === 200) {
        console.log('JS error logged');
    } else if (xhr.status !== 200) {
        console.error('Failed to log JS error.');
        console.error(xhr);
        console.error(xhr.status);
        console.error(xhr.responseText);
    }
};
xhr.send(JSON.stringify(error_data));

JSFiddle:

https://jsfiddle.net/nzfvm44d/

Список літератури:


3
Варто згадати, що Firefox не повертає повідомлення про помилку, коли throwвоно робиться вручну. stackoverflow.com/questions/15036165/…
Себас

2
Чудова відповідь. Ви можете реалізувати "Повідомити про цю помилку через ajax" з таким пакетом, як JSNLog, який робить журнал ajax та сервер для вас.
користувач1147862

4
На додаток до цієї відповіді я додав об’єкт помилки, щоб я міг отримати слід стека. stackoverflow.com/a/20972210/511438 . Тепер я можу розвиватися з більшою кількістю відгуків, оскільки мої помилки розробників відображаються як поле вгорі сторінки (як я його створив).
Валамас

Хіба не було б краще упакувати вашу програму onerror у блок "try-catch (ignore"), не даючи вашому onerror () викинути ще більше помилок? (не те, що тут справа, але просто для переконання)
Пітер Де Бі

1
@ super1ha1 Чи можете ви надати jsfiddle? Я не думаю, що веб-переглядачі можуть внести переломні зміни, коли обробник подій onerror перестає запускати події. Ви можете спробувати цю jsfiddle, щоб побачити, як працює обробник onerror: jsfiddle.net/nzfvm44d Це все ще працює для мене у версії Chrome 62.0.3202.94 (Official Build) (64-розрядна версія).
Сем

38

складне поводження з помилками

Якщо обробка помилок є дуже складною і тому може призвести до помилки, корисно додати прапор із зазначенням, що ви вже перебуваєте в режимі помилок. Так:

var appIsHandlingError = false;

window.onerror = function() {
    if (!appIsHandlingError) {
        appIsHandlingError = true;
        handleError();
    }
};

function handleError() {
    // graceful error handling
    // if successful: appIsHandlingError = false;
}

Інакше ти можеш опинитися в нескінченному циклі.


29
Або більш безпечним способом буде використання пробного handleErrorметоду навколо методу.
Айдіакапі

8
Ви не можете використовувати спробу лову, якщо у вас є асинхронний дзвінок при обробці помилок. Тож він залишається хорошим рішенням
Емріс Міруін

@EmrysMyrooin Все-таки це спричинить синхронізацію чи цикл асинхронізації через обробку помилок та, ймовірно, збій без інформації про стек.
inf3rno

1
Я думаю, прапор слід скинути в якийсь момент, правда? Я вважаю, що нам потрібно спробувати / наздогнати, якщо великий, і переконайтесь, що прапор скинуто прямо перед тим, як залишити метод onerror. В іншому випадку буде оброблена лише одна помилка.
Себ

23

Спробуйте Atatus, який забезпечує розширене відстеження помилок та реальний моніторинг користувачів для сучасних веб-додатків.

https://www.atatus.com/

Дозвольте мені пояснити, як отримати стек-треки, які достатньо повні у всіх браузерах.

Помилка керування в JavaScript

Сучасні Chrome і Opera повністю підтримують специфікацію проекту HTML 5 для ErrorEvent і window.onerror. В обох цих веб-переглядачах ви можете або window.onerrorправильно використовувати прив'язку до події "помилка":

// 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);
})

На жаль, Firefox, Safari та IE все ще існують, і ми також повинні їх підтримувати. Оскільки стек-трек недоступний вwindow.onerror ми повинні зробити трохи більше роботи.

Виявляється, єдине, що ми можемо зробити, щоб отримати стеки від помилок - це загорнути весь наш код у try{ }catch(e){ }блок і потім подивитися e.stack. Ми можемо зробити процес дещо простішим за допомогою функції під назвою обгортка, яка приймає функцію і повертає нову функцію з хорошим поводженням з помилками.

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;
};

Це працює. Будь-яка функція, яку ви завертаєте вручну, матиме хорошу обробку помилок, але виявляється, що ми в більшості випадків можемо виконати її автоматично.

Змінивши глобальне визначення, addEventListenerщоб воно автоматично завершило зворотний виклик, ми можемо автоматично вставити try{ }catch(e){ }навколо більшості коду. Це дозволяє діючий код продовжувати працювати, але додає якісне відстеження виключень.

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

Ми також повинні переконатися, що це removeEventListenerпродовжує працювати. Наразі це не стане, оскільки аргумент до цього addEventListenerбуде змінено. Знову нам потрібно лише виправити це на prototypeоб’єкті:

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

Передайте дані про помилки у свій бекенд

Ви можете надіслати дані про помилки, використовуючи тег зображення наступним чином

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;
}

Відмова: Я веб-розробник за адресою https://www.atatus.com/ .


Що таке yourserver.com/jserror ? REST, веб-сервіс, сервіс Wcf ? Будь-який простір про бекенд?
Кікенет

Якщо ви хочете відправити js помилки з браузера користувача на ваш сервер. ви повинні написати свій бекенд ( http://yourserver.com), щоб отримувати та зберігати. Якщо ви вибрали atatus.com , вам нічого не потрібно робити. Просто включіть на свою сторінку два рядки сценарію.
Фізер Хан

18

Здається, window.onerrorце не забезпечує доступ до всіх можливих помилок. Зокрема, він ігнорує:

  1. <img> помилки завантаження (відповідь> = 400).
  2. <script> помилки завантаження (відповідь> = 400).
  3. глобальні помилки, якщо у вашому додатку багато інших бібліотек, які також маніпулюють window.onerror невідомим способом (jquery, angular тощо).
  4. напевно, у багатьох випадках я не стикався після дослідження цього (iframes, переповнення стека тощо).

Ось початок сценарію, який виявляє багато цих помилок, так що ви можете додавати більш надійні налагодження у вашому додатку під час розробки.

(function(){

/**
 * Capture error data for debugging in web console.
 */

var captures = [];

/**
 * Wait until `window.onload`, so any external scripts
 * you might load have a chance to set their own error handlers,
 * which we don't want to override.
 */

window.addEventListener('load', onload);

/**
 * Custom global function to standardize 
 * window.onerror so it works like you'd think.
 *
 * @see http://www.quirksmode.org/dom/events/error.html
 */

window.onanyerror = window.onanyerror || onanyerrorx;

/**
 * Hook up all error handlers after window loads.
 */

function onload() {
  handleGlobal();
  handleXMLHttp();
  handleImage();
  handleScript();
  handleEvents();
}

/**
 * Handle global window events.
 */

function handleGlobal() {
  var onerrorx = window.onerror;
  window.addEventListener('error', onerror);

  function onerror(msg, url, line, col, error) {
    window.onanyerror.apply(this, arguments);
    if (onerrorx) return onerrorx.apply(null, arguments);
  }
}

/**
 * Handle ajax request errors.
 */

function handleXMLHttp() {
  var sendx = XMLHttpRequest.prototype.send;
  window.XMLHttpRequest.prototype.send = function(){
    handleAsync(this);
    return sendx.apply(this, arguments);
  };
}

/**
 * Handle image errors.
 */

function handleImage() {
  var ImageOriginal = window.Image;
  window.Image = ImageOverride;

  /**
   * New `Image` constructor. Might cause some problems,
   * but not sure yet. This is at least a start, and works on chrome.
   */

  function ImageOverride() {
    var img = new ImageOriginal;
    onnext(function(){ handleAsync(img); });
    return img;
  }
}

/**
 * Handle script errors.
 */

function handleScript() {
  var HTMLScriptElementOriginal = window.HTMLScriptElement;
  window.HTMLScriptElement = HTMLScriptElementOverride;

  /**
   * New `HTMLScriptElement` constructor.
   *
   * Allows us to globally override onload.
   * Not ideal to override stuff, but it helps with debugging.
   */

  function HTMLScriptElementOverride() {
    var script = new HTMLScriptElement;
    onnext(function(){ handleAsync(script); });
    return script;
  }
}

/**
 * Handle errors in events.
 *
 * @see http://stackoverflow.com/questions/951791/javascript-global-error-handling/31750604#31750604
 */

function handleEvents() {
  var addEventListenerx = window.EventTarget.prototype.addEventListener;
  window.EventTarget.prototype.addEventListener = addEventListener;
  var removeEventListenerx = window.EventTarget.prototype.removeEventListener;
  window.EventTarget.prototype.removeEventListener = removeEventListener;

  function addEventListener(event, handler, bubble) {
    var handlerx = wrap(handler);
    return addEventListenerx.call(this, event, handlerx, bubble);
  }

  function removeEventListener(event, handler, bubble) {
    handler = handler._witherror || handler;
    removeEventListenerx.call(this, event, handler, bubble);
  }

  function wrap(fn) {
    fn._witherror = witherror;

    function witherror() {
      try {
        fn.apply(this, arguments);
      } catch(e) {
        window.onanyerror.apply(this, e);
        throw e;
      }
    }
    return fn;
  }
}

/**
 * Handle image/ajax request errors generically.
 */

function handleAsync(obj) {
  var onerrorx = obj.onerror;
  obj.onerror = onerror;
  var onabortx = obj.onabort;
  obj.onabort = onabort;
  var onloadx = obj.onload;
  obj.onload = onload;

  /**
   * Handle `onerror`.
   */

  function onerror(error) {
    window.onanyerror.call(this, error);
    if (onerrorx) return onerrorx.apply(this, arguments);
  };

  /**
   * Handle `onabort`.
   */

  function onabort(error) {
    window.onanyerror.call(this, error);
    if (onabortx) return onabortx.apply(this, arguments);
  };

  /**
   * Handle `onload`.
   *
   * For images, you can get a 403 response error,
   * but this isn't triggered as a global on error.
   * This sort of standardizes it.
   *
   * "there is no way to get the HTTP status from a 
   * request made by an img tag in JavaScript."
   * @see http://stackoverflow.com/questions/8108636/how-to-get-http-status-code-of-img-tags/8108646#8108646
   */

  function onload(request) {
    if (request.status && request.status >= 400) {
      window.onanyerror.call(this, request);
    }
    if (onloadx) return onloadx.apply(this, arguments);
  }
}

/**
 * Generic error handler.
 *
 * This shows the basic implementation, 
 * which you could override in your app.
 */

function onanyerrorx(entity) {
  var display = entity;

  // ajax request
  if (entity instanceof XMLHttpRequest) {
    // 400: http://example.com/image.png
    display = entity.status + ' ' + entity.responseURL;
  } else if (entity instanceof Event) {
    // global window events, or image events
    var target = entity.currentTarget;
    display = target;
  } else {
    // not sure if there are others
  }

  capture(entity);
  console.log('[onanyerror]', display, entity);
}

/**
 * Capture stuff for debugging purposes.
 *
 * Keep them in memory so you can reference them
 * in the chrome debugger as `onanyerror0` up to `onanyerror99`.
 */

function capture(entity) {
  captures.push(entity);
  if (captures.length > 100) captures.unshift();

  // keep the last ones around
  var i = captures.length;
  while (--i) {
    var x = captures[i];
    window['onanyerror' + i] = x;
  }
}

/**
 * Wait til next code execution cycle as fast as possible.
 */

function onnext(fn) {
  setTimeout(fn, 0);
}

})();

Це можна використовувати так:

window.onanyerror = function(entity){
  console.log('some error', entity);
};

Повний сценарій має реалізацію за замовчуванням, яка намагається роздрукувати напівчитану "відображувану" версію сутності / помилки, яку вона отримує. Можна використовувати для натхнення для конкретного додатка, який обробляє помилки. Реалізація за замовчуванням також зберігає посилання на останні 100 об'єктів помилок, тому ви можете перевірити їх у веб-консолі після того, як вони виникають як:

window.onanyerror0
window.onanyerror1
...
window.onanyerror99

Примітка. Це працює за допомогою перекриття методів на декількох веб-переглядачах / власних конструкторах. Це може мати небажані побічні ефекти. Однак це було корисно використовувати під час розробки, щоб з’ясувати, де виникають помилки, надсилати журнали до служб, таких як NewRelic або Sentry під час розробки, щоб ми могли вимірювати помилки під час розробки та під час постановки, щоб ми могли налагодити те, що відбувається на більш глибокий рівень. Потім його можна вимкнути у виробництві.

Сподіваюсь, це допомагає.


1
Мабуть зображення викликати подія помилки: stackoverflow.com/a/18152753/607033
inf3rno

6
// display error messages for a page, but never more than 3 errors
window.onerror = function(msg, url, line) {
if (onerror.num++ < onerror.max) {
alert("ERROR: " + msg + "\n" + url + ":" + line);
return true;
}
}
onerror.max = 3;
onerror.num = 0;

6

Слід також зберегти попередньо пов’язаний зворотний виклик onerror

<script type="text/javascript">

(function() {
    var errorCallback = window.onerror;
    window.onerror = function () {
        // handle error condition
        errorCallback && errorCallback.apply(this, arguments);
    };
})();

</script>

3

Якщо ви хочете, щоб уніфікований спосіб поводження як з помилками помилок, так і з неуспішними відхиленнями обіцянок, ви можете ознайомитися з невловимою бібліотекою .

EDIT

<script type="text/javascript" src=".../uncaught/lib/index.js"></script>

<script type="text/javascript">
    uncaught.start();
    uncaught.addListener(function (error) {
        console.log('Uncaught error or rejection: ', error.message);
    });
</script>

Це слухає вікно. unhandledrejection на додаток до window.onerror.


2
Ленс Поллард у відповіді вище згадував декілька помилок, які звичайний onerror не справляється. Чи обробляє їх бібліотека? Якщо деякі з них, будь ласка, вкажіть, які?
Майкл Фрейджім

@ MiFreidgeimSO-stopbeingevil Просто перевірили вихідний код - на жаль, це не так. Олександр Олейников: Було б дивовижно, якщо ви можете підтримати це - я тоді перетворять свій нижчий внесок на ревізію ;-)
brillout

2

Я рекомендую спробувати Trackjs .

Це реєстрація помилок як послуга.

Це надзвичайно просто налаштувати. Просто додайте один рядок <script> на кожну сторінку, і все. Це також означає, що видалити їх буде надзвичайно просто, якщо ви вирішите, що вам це не подобається.

Є й інші сервіси, такі як Sentry (який є відкритим кодом, якщо ви можете розмістити власний сервер), але він не робить те, що робить Trackjs. Trackjs записує взаємодію користувача між його браузером та веб-сервером, щоб ви могли фактично простежити кроки користувача, що призвели до помилки, на відміну від лише посилання на номер файлу та рядка (а може бути і стеження стека).


2
Схоже, у TrackJS більше немає вільного рівня , хоча є безкоштовна пробна версія.
pkaeding

Це добре для відстеження та попередження про помилки, але насправді не вирішує частину обробки. В ідеалі я думаю, що запитувач шукає спосіб впоратися з цими помилками, щоб решта коду все-таки працювала.
wgp

Що робити, якщо ви зафіксували подію помилки та додали виклик xhr до реєстратора зі слідом стека та станом програми? Як краще trackjs?
inf3rno

2
@ inf3rno - це більше, ніж слід стека, який просто відстежує початок помилки. TrackJS буде відслідковувати весь сеанс користувача, щоб ви могли бачити, що призвело до цього. ось скріншот як приклад trackjs.com/assets/images/screenshot.png
kane

1

Ви слухаєте подію onerror, призначивши функцію window.onerror:

 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 {
            alert(msg, url, lineNo, columnNo, error);
        }   
      return false; 
  };
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.