Xatoliklarni boshqarish

Xatoliklarni boshqarish, try...catch

Kod qanchalik yaxshi yozilmasin, xatolar muqarrar. Sababi ko'p: bizning xatomiz, kutilmagan foydalanuvchi kiritmasi, noto'g'ri server javobi va yana minglab holatlar. Odatda xato bo'lganda skript "o'ladi" (to'xtaydi) va konsolga xato chiqadi. Ammo try...catch sintaksisi bunday xatolarni "ushlab olib", skriptni to'xtatmasdan, oqilona harakat qilishga imkon beradi.

try...catch sintaksisi

try...catch konstruksiyasi ikki asosiy blokdan iborat: try va so'ng catch:

try {

  // kod...

} catch (err) {

  // xatoni qayta ishlash

}

U quyidagicha ishlaydi:

  1. Avval try {...} ichidagi kod bajariladi;
  2. Agar xato bo'lmasa, catch(err) bloki e'tiborsiz qoldiriladi: bajarilish try oxiriga yetadi va catchdan sakrab o'tadi;
  3. Agar xato yuz bersa, try bajarilishi o'sha joyda to'xtaydi va boshqaruv catch(err) blokining boshiga o'tadi. err o'zgaruvchisi (istalgan nom bo'lishi mumkin) nima yuz berganini tavsiflovchi xato obyektini saqlaydi.

Shunday qilib, try {...} ichidagi xato skriptni o'ldirmaydi โ€” bizda uni catch ichida qayta ishlash imkoni bor.

Xatosiz misol โ€” catch umuman ishlamaydi:

xatosiz.js
// natija shu yerda chiqadi

Endi xatoli misol:

xatoli.js
// natija shu yerda chiqadi

E'tibor bering: xatodan keyingi (2) qatori umuman bajarilmadi. JavaScript darhol catchga sakradi, skriptni butunlay to'xtatmadi.

try...catch faqat runtime xatolar uchun ishlaydi

try...catch ishlashi uchun kod bajarilayotgan ("runnable") bo'lishi shart. Ya'ni u to'g'ri JavaScript bo'lishi kerak.

Agar kod sintaktik jihatdan noto'g'ri bo'lsa (masalan, yopilmagan qavs โ€” {{{{), try...catch yordam bermaydi. JavaScript dvigateli avval kodni o'qiydi (parse qiladi), so'ng ishga tushiradi. Parse bosqichida topilgan xatolar "parse-time" xatolari deb ataladi va ular try...catch ichida ushlanmaydi โ€” chunki dvigatel bu kodni tushunmaydi.

Demak, try...catch faqat to'g'ri kodda yuz beradigan xatolarni ushlaydi. Bunday xatolar "runtime errors" (ishga tushish paytidagi xatolar) yoki ba'zan "exceptions" (istisnolar) deb ataladi.

try...catch sinxron ishlaydi

Agar xato "rejalashtirilgan" kodda (masalan, setTimeout ichida) yuz bersa, uni try...catch ushlay olmaydi:

try {
  setTimeout(function() {
    noSuchVariable; // skript bu yerda to'xtaydi
  }, 1000);
} catch (err) {
  console.log("ushlanmaydi!");
}

Chunki setTimeout ichidagi funksiya keyinroq, dvigatel try...catch konstruksiyasidan allaqachon chiqib ketgandan so'ng bajariladi. Xatoni ushlash uchun try...catch aynan o'sha funksiya ichida bo'lishi kerak:

setTimeout(function() {
  try {
    noSuchVariable; // endi try...catch xatoni ushlaydi!
  } catch (err) {
    console.log("xato bu yerda ushlandi");
  }
}, 1000);
Asinxron kod (async/await) bilan ishlaganda ham xuddi shu qoida amal qiladi, u haqda keyingi boblarda batafsil to'xtalamiz.

Error obyekti

Xato yuz berganda, JavaScript uni tavsiflovchi obyekt yaratadi. So'ng bu obyekt catchga argument sifatida uzatiladi:

try {
  // ...
} catch (err) { // <-- "xato obyekti", boshqa nom ham berilsa bo'ladi
  // ...
}

O'rnatilgan xatolar uchun error obyekti ikkita asosiy xususiyatga ega:

Ko'p muhitlarda yana bir standart bo'lmagan, lekin keng qo'llab-quvvatlanadigan xususiyat bor:

error-obyekti.js
// natija shu yerda chiqadi
Xato obyektini butunicha ham chiqarish mumkin: console.log(err) yoki matn sifatida console.log(err + "") โ€” u name: message ko'rinishida chiqadi.

Amaliy misol: JSON.parse xatosi

Amalda try...catch eng ko'p ma'lumotni o'qiyotganda kerak bo'ladi. Masalan, serverdan yoki foydalanuvchidan kelgan JSON matnini JSON.parse bilan tahlil qilamiz. Agar matn buzuq bo'lsa, JSON.parse xato tashlaydi va skript to'xtaydi. Buni oldini olamiz:

json-parse.js
// natija shu yerda chiqadi

Endi to'g'ri JSON bilan solishtiring โ€” catch ishlamaydi:

json-togri.js
// natija shu yerda chiqadi

throw operatori โ€” o'z xatolaringizni tashlash

Yuqoridagi JSON misolida qiziq holat bor: JSON matni to'g'ri bo'lishi mumkin, lekin unda kerakli maydon (masalan, name) yo'q bo'lishi mumkin. Bunda JSON.parse xato tashlamaydi โ€” chunki JSON o'zi haqiqiy. Lekin biz uchun bu xato. Bunday holatlarni o'zimiz aniqlab, o'z xatomizni tashlashimiz kerak. Buning uchun throw operatori ishlatiladi.

throw <xato obyekti>

Texnik jihatdan istalgan qiymatni throw bilan tashlash mumkin (son, satr), lekin eng yaxshi amaliyot โ€” Error yoki uning avlodlaridan foydalanish. JavaScriptda standart o'rnatilgan xato konstruktorlari bor:

Ularning barchasi bir xil chaqiriladi โ€” argument sifatida message beriladi:

error-yaratish.js
// natija shu yerda chiqadi

Endi throw yordamida to'liq misolni ko'ramiz โ€” JSON to'g'ri, lekin name maydoni yo'q:

throw-misol.js
// natija shu yerda chiqadi

Diqqat qiling: catch bloki ikkala turdagi xatoni ham ushlaydi โ€” JSON.parse tashlaganini ham, biz throw qilganimizni ham. Bu juda qulay: bitta joyda barcha xatolar bilan ishlaymiz.

Qayta tashlash (rethrowing)

Yuqoridagi catch hamma xatolarni ushlaydi. Lekin bu muammo tug'diradi: catch(err) faqat biz kutgan xatoni (to'liq emas ma'lumot) qayta ishlashi kerak edi. Agar u yerda kutilmagan boshqa xato bo'lsa-chi? Masalan, biz kod yozishda xato qilib, aniqlanmagan o'zgaruvchini chaqirsak โ€” bu ReferenceErrorni ham "JSON Xato" deb ko'rsatib qo'yamiz, bu esa debugging'ni qiyinlashtiradi.

Yechim "qayta tashlash" (rethrowing) texnikasi: catch faqat o'zi biladigan xatolarni qayta ishlaydi, qolganlarini esa throw err orqali "yuqoriga" uzatadi.

  1. catch hamma xatoni oladi;
  2. catch (err) {...} blokida biz errni tekshiramiz โ€” bu qanday xato?;
  3. Agar biz uni qanday qayta ishlashni bilmasak โ€” throw err qilamiz.

Odatda tur instanceof operatori bilan tekshiriladi:

rethrow.js
// natija shu yerda chiqadi

Yuqorida blabla() mavjud emas, shuning uchun ReferenceError yuz beradi. U SyntaxError emas, shuning uchun throw err ishga tushadi va xato "yuqoriga" tashlanadi. Agar bu try...catch tashqarisida yana bir try...catch bo'lsa, xatoni o'sha ushlaydi. Aks holda skript to'xtaydi.

Rethrowing qoidasi: catch faqat o'zi tushunadigan xatolar bilan ishlashi, boshqalarini esa qayta tashlashi kerak. Bu "begona" xatolarni yashirib qo'yishning oldini oladi.

try...catch...finally

try...catchning to'liq shakli yana bir ixtiyoriy blokka ega โ€” finally. Agar u mavjud bo'lsa, u har doim bajariladi:

try {
  // ... kodni bajarishga urinamiz ...
} catch (err) {
  // ... xatolarni qayta ishlaymiz ...
} finally {
  // ... har doim bajariladi ...
}
finally.js
// natija shu yerda chiqadi

finally odatda biror ishni boshlaganimizda va uni xato bo'ladimi-yo'qmi, baribir yakunlashimiz kerak bo'lganda ishlatiladi โ€” masalan, o'lchashni to'xtatish, ulanishni yopish, yuklanish indikatorini o'chirish.

finally-vaqt.js
// natija shu yerda chiqadi
finally hatto try yoki catch ichida return bo'lsa ham ishlaydi! return qiymatni tayyorlaydi, lekin funksiya haqiqatan chiqishidan oldin finally bajariladi.
finally-return.js
// natija shu yerda chiqadi
catch blokini tushirib qoldirsa ham bo'ladi: try...finally shaklida. Bunda xatolar ushlanmaydi (yuqoriga tashlanadi), lekin finally baribir ishlaydi โ€” masalan, tozalash uchun.

Global catch (qisqacha)

Agar try...catch tashqarisida o'lik xato yuz bersa, skript "o'ladi". Ba'zan bunday ushlanmagan xatolarni bir joyda umumiy tarzda ushlash foydali โ€” masalan, jurnalga yozish (log) yoki foydalanuvchiga xabar berish uchun.

Muhitga qarab mexanizm boshqacha:

Global ushlagich โ€” oxirgi chora. U skriptni to'liq "tuzatmaydi": xato allaqachon yuz bergan, dastur holati buzuq bo'lishi mumkin. Uni asosan xatolarni qayd qilish (logging) va monitoring xizmatlariga (masalan, Sentry) yuborish uchun ishlating, dasturning oddiy oqimi uchun emas.

Xulosa