با استفاده از window.onerror، خطا های جاوا اسکریپت را یافته و اطلاع دهید

گردآوری و تالیف : عرفان کاکایی
تاریخ انتشار : 15 خرداد 1397
دسته بندی ها : جاوا اسکریپت

Onerror یک رویداد مرورگر ویژه است، که هر موقع خطایی در جاوا اسکریپت بروز دهد، اجرا می شود. این یکی از ساده ترین روش ها برای گرفتن خطا های سمت کاربر، و گزارش آنها به سرور شما است. همچنین یکی از مکانیزم های اصلی است، که Senry’s client JavaScript integration (raven-js) با آن کار می کند.

می توانید با اختصاص دادن یک تابع به window.onerror خطا را بررسی کنید.

window.onerror = function(msg, url, lineNo, columnNo, error) {
  // ... handle error ...
  return false;
}

وقتی که خطایی رخ می دهد، این آرگومان ها به تابع ارسال می شوند:

  • msg - پیامی که با خطا همراه است.
  • url - URL اسکریپت یا سند همراه با خطا.
  • lineNo - شماره خط (اگر در دسترس است)
  • columnNo - شماره ستون (اگر در دسترس است)
  • error - آبجکت خطا همراه با خود خطا (اگر در دسترس است)

آرگومان اول به شما می گوید که خطا در کدام اسکریپت، خط و ستون بروز داده است. آرگومان آخر، یعنی آبجکت خطا، احتمالا با ارزش ترین آرگومان است. حال بیایید علت آن را ببینیم.

جدول محتویات:

  1. آبجکت خطا و error.stack
  2. سازگاری با مرورگر
  3. Polyfill کردن window.error با try/catch
  4. ارسال خطا به سرور شما
  5. خلاصه

آبجکت خطا و error.stack

در نگاه اول، آبجکت خطا آنچنان خاص نیست. این آبجکت شامل سه ویژگی (Property) استاندارد می شود: message، filename و lineNumber. مقدار های اضافی که از قبل توسط window.onerror به شما رسانده شده اند.

بخش مقدار، یک ویژگی غیر استاندارد است: Error.prototype.stack. این property Stack به شما می گوید که در هنگام بروز خطا، هر فریم برنامه در کدام موقعیت بود. Error stack trace می تواند یک بخش حیاتی دیباگ کردن باشد، و با وجود غیر استاندارد بودن، این ویژگی در هر مرورگر مدرنی قابل دسترسی است.

در زیر، مثالی از property Stack آبجکت خطا را در گوگل کروم 46 مشاهده می کنید:

"Error: foobar\n    at new bar (<anonymous>:241:11)\n    at foo (<anonymous>:245:5)\n    at <anonymous>:250:5\n    at <anonymous>:251:3\n    at <anonymous>:267:4\n    at callFunction (<anonymous>:229:33)\n    at <anonymous>:239:23\n    at <anonymous>:240:3\n    at Object.InjectedScript.\_evaluateOn (<anonymous>:875:140)\n    at Object.InjectedScript.\_evaluateAndWrap (<anonymous>:808:34)"

خواندنش سخت است؟ property Stack در واقع یک رشته قالب بندی نشده است.

پس از قالب بندی، به این شکل در می آید:

Error: foobar
  at new bar (<anonymous>:241:11)
  at foo (<anonymous>:245:5)
  at callFunction (<anonymous>:229:33)
  at Object.InjectedScript._evaluateOn (<anonymous>:875:140)
  at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34)

پس از این که قالب بندی شد، به راحتی می توان دید که چگونه property Stack می تواند در دیباگ کردن حیاتی باشد.

تنها یک نکته وجود دارد: Stack property غیر استاندارد است، و پیاده سازی اش میان مرورگر ها متفاوت است. برای مثال، این همان Stack trace در Internet Explorer 11 است:

Error: foobar
  at bar (Unknown script code:2:5)
  at foo (Unknown script code:6:5)
  at Anonymous function (Unknown script code:11:5)
  at Anonymous function (Unknown script code:10:2)
  at Anonymous function (Unknown script code:1:73)

نه تنها قالب بندی هر فریم متفاوت است، بلکه فریم ها جزئیات کمتری نیز دارند. برای مثال، کروم تشخیص می دهد که یک کیبورد جدید متصل شده است، و بر روی ارزیابی ها نیز دقت بیشتری دارد. و این تنها مقایسه IE 11 و کروم است، دیگر مرورگر های مشابه قالب بندی و جزئیات بسیار متفاوتی دارند.

خوشبختانه، ابزاری وجود دارند که Stack property ها را نرمال سازی می کنند، تا در میان مرورگر ها نامتناقص باشد. برای مثال، raven-js از TraceKit برای نرمال سازی رشته های خطا استفاده می کند. همچنین stacktrace.js و تعدادی پروژه دیگر نیز برای این کار وجود دارند.

سازگاری با مرورگر

مدتی می شوند که window.onerror در میان مرورگر ها در دسترس است. می توانید آن را در مرورگر های قدیمی ای مثل IE6 و Firefox 2 بیابید.

مشکل اینجاست که هر مرورگر window.onerror را به طور متفاوتی پیاده سازی می کند. به خصوص در این که چه تعداد آرگومان هایی به onerror ارسال می شوند، و این که ساختار آنها چگونه است.

در این جدول، آرگومان هایی که در اکثر مرورگر ها به onerror ارسال می شوند، دیده می شود:

خطا های جاوا اسکریپت

احتمالا برای شما نیز جای تعجب ندارد که اینترنت اکسپلورر 8، 9 و 10 پشتیبانی محدودی نسبت به onerror دارند. اما شاید این که Safari پس از نسخه 10 (در سال 2016) پشتیبانی onerror را اضافه کرد، شما را متعجب کند. به علاوه، موبایل های قدیمی که هنوز از مرورگر اندروید استفاده می کنند، (که حالا با گوگل کروم جایگزین شده است) هنوز هم نمی توانند آبجکت خطا را ارسال کنند.

بدون آبجکت خطا، هیچ Stack trace property ای وجود ندارد. این به این معنی است که این مرورگر ها نمی توانند اطلاعات Stack با ارزشی را از خطا های یافت شده توسط onerror را دریافت کنند.

Polyfill کردن window.error با try/catch

حال یک راه جانبی ای نیز وجود دارد. می توانید کد خود را در برنامه داخل یک try/catch بیندازید، و خطا را خودتان بیابید. این آبجکت خطا Stack property مورد نظر ما را در هر مرورگر مدرنی شامل می شود.

این متود کمکی را در نظر بگیرید. Invoke تابعی را با آرایه ای از آرگومان ها بر روی یک آبجکت فراخوانی می کند:

function invoke(obj, method, args) {
  return obj[method].apply(this,args);
}


invoke(Math, 'max', [1,2]); // returns 2

یک invoke دیگر نیز در اینجا وجود دارد، که این بار داخل یک try/catch قرار گرفته، تا هر خطای یافت شده ای را بگیرد:

function invoke(obj, method, args) {
  try {
    return obj[method].apply(this,args);
  } catch(e) {
    captureError(e);// report the error
    throw e;// re-throw the error
  }
}

invoke(Math,'highest',[1,2]); // throws error, no method Math.highest

البته، هر بار انجام این کار بسیار پر زحمت است. می توانید این کار را با تولید یک تابع کاربری آسان تر کنید:

function wrapErrors(fn) {
  // don't wrap function more than once
  if(!fn.__wrapped__) {
    fn.__wrapped__ = function() {
      try{
        return fn.apply(this,arguments);
      }catch(e){
        captureError(e);// report the error
        throwe;// re-throw the error
      }
    };
  }

  return fn.__wrapped__;
}

var invoke = wrapErrors(function(obj, method, args) {
  returnobj[method].apply(this,args);
});

invoke(Math,'highest',[1,2]);// no method Math.highest

از آنجا که جاوا اسکریپت تک رشته ای است، نیازی نیست این کار را در همه جا انجام دهید. فقط در هنگام شروع هر Stack جدید، کافی است.

این یعنی شما باید در مواقع زیر این توابع را تعریف کنید:

  • در هنگام شروع برنامه، که اگر از jQuery استفاده کنید، آماده است.
  • در Event Handler ها.
  • Callback های زمان بندی شده.

برای مثال:

$(wrapErrors(function () {// application start

  doSynchronousStuff1();// doesn't need to be wrapped

  setTimeout(wrapErrors(function () {
    doSynchronousStuff2();// doesn't need to be wrapped
  }));

  $('.foo').click(wrapErrors(function () {
    doSynchronousStuff3();// doesn't need to be wrapped
  }));

}));

اگر این کار زیاد به نظر می رسد، نگران نباشید. اکثر کتابخانه های گزارش خطا مکانیزم هایی برای آرگومان های داخلی توابع مثل addEventListener و setTimeout دارند، تا مجبور نباشید هر بار خودتان این توابع را فراخوانی کنید. Raven-js نیز دقیقا همین کار را انجام می دهد.

ارسال خطا به سرور شما

حال شما کار خود را انجام داده اید. وارد window.onerror شده اید، و به علاوه، توابع خود را در try/catch قرار داده اید، تا حداکثر تعداد خطا را دریابید.

حال یک قدم پایانی باقی مانده است: ارسال اطلاعات خطا به سرور شما. برای انجام این کار، باید نوعی وب سرویس اطلاع رسانی راه اندازی کنید که داده های خطای شما را بر روی HTTP می پذیرد، و آن را بر روی فایلی می نویسد یا در دیتابیسی ذخیره می کند.

اگر این وب سرویس بر روی دامنه مشابه برنامه شما نیست، از XMLHttpRequest استفاده کنید. در مثال زیر، از تابع AJAX در jQuery برای ارسال داده ها به سرور استفاده می کنیم.

function captureError(ex){

  var errorData = {
    name:ex.name,// e.g. ReferenceError
    message:ex.line,// e.g. x is undefined
    url:document.location.href,
    stack:ex.stack// stacktrace string; remember, different per-browser!
  };

  $.post('/logger/js/',{
    data:errorData
  });

}

با توجه به این، اگر باید خطای خود را بین ریشه های مختلفی ارسال کنید، نقطه پایانی گزارش دهی شما باید از Cross Origin Resource Sharing (CORS) پشتیبانی کند.

خلاصه

حال که تا اینجا پیش آمده اید، تمام ابزار مورد نیاز برای ساخت کتابخانه گزارش دهی خطای خود و ادغام کردن آن با برنامه تان را دارید:

  • onerror چگونه کار می کند، و چه مرورگر هایی را پشتیبانی می کند.
  • چگونه از try/catch در جایی که onerror وجود ندارد، استفاده کنیم.
  • ارسال داده های خطا به سرور های شما.

البته، اگر نمی خواهید تمام این سختی ها را بگذرانید، تعداد زیادی ابزار اوپن سورس برای انجام این کار های سمت کاربر وجود دارند.

منبع

مقالات پیشنهادی

دیباگ پروژه های جاوا اسکریپت با استفاده از Source Map ها

امروزه، کدی که برای برنامه خود می نویسید معمولا همان کدی نیست که در هنگام تولید استفاده می شود و توسط مرورگر ترجمه می شود. شاید کد خود را با زبانی می...

ساخت کامل اپلیکیشن های جاوااسکریپتی به راحتی با CxJS

اپلیکیشن های مبتنی بر جاوااسکریپت را می توان در هر سطحی از وبسایت ها مشاهده کرد. از یک وبسایت کوچک گرفته تا یک وبسایت حرفه‌ای و پیشرفته

چگونه با استفاده از جاوااسکریپت پیش‌نمایش‌های چندگانه ایجاد کنیم؟

در این مطلب آموزشی قصد داریم با استفاده از جاوااسکریپت و رویدادهای ماوس یک چیز جالب و مفید را درست کنیم: قصد داریم تصاویر بندانگشتی را ایجاد کنیم که ب...

با جاوا اسکریپت سلفی بگیرید

در این آموزش می خواهیم به شما نشان بدیم که چطور یک اپلیکیشن عکاسی جاوا اسکریپت بسازید که با استفاده از دوربین گوشی, لپ تاپ یا دسکتاپ عکس بگیره. ما تعد...