IlmHamroh
JavaScript Full-stack/24-qism. Node.js46/55-dars18 daqiqa
Mundarija (28)

Node.js stack trace, source map va error.cause: xato xabarini to'g'ri o'qish

Qisqacha: Stack trace — xato paydo bo'lgan joydan boshlab "kim kimni chaqirgan" ro'yxati. Yuqoridagi qator — xato joyi, pastdagilar — chaqiruvchilar. await zanjiri at async … qatorlari bilan saqlanadi, eski callback'lar (setTimeout) esa zanjirni uzadi. tsc bilan build qilingan kodda qatorlar dist/*.js ni ko'rsatadi — .ts ga qaytarish uchun source map kerak: --enable-source-maps yoki module.setSourceMapsSupport(true). Kanon topilmasi: setSourceMapsSupport ni statik importlardan keyin chaqirsangiz, ular uchun ishlamaydi — kodni dinamik import() bilan yuklash kerak. error.cause xatoga asl sababni biriktiradi: CBU API: tarmoq xatosi ← fetch failed ← connect ECONNREFUSED.

Bu darsda

  • Stack trace'ni yuqoridan pastga o'qib, xato qayerda va qanday yo'l bilan paydo bo'lganini topasiz.
  • Async stack trace qachon saqlanishini va qachon uzilishini bilasiz.
  • Error.captureStackTrace, Error.stackTraceLimit, --stack-trace-limit bilan stackni boshqarasiz.
  • Source map'ni Node'da yoqasiz va ESM import tartibi tuzog'ini tushuntira olasiz.
  • error.cause zanjirini va AggregateError ni ishlatasiz; valyuta ning holat 46 ini commit qilasiz.

Oldin bilishingiz kerak: Stack trace va JavaScript xato turlari, throw va Error obyekti, O'z xato klasslaringiz, Source map'lar, Node'ni debug qilish.

1. Nega bu kerak?

Foydalanuvchi yozdi: "valyuta tahlil ishlamayapti". Siz NODE_DEBUG=valyuta bilan stackni so'radingiz va oldingiz:

text
Error: 1-qator "sana,kod,nomi,nominal,kurs,farq" bo'lishi kerak, topildi: "yomon,sarlavha"
    at tahlilQil (file:///C:/Users/…/valyuta/dist/tahlil.js:42:23)

dist/tahlil.js:42 — tsc yaratgan fayl. Siz esa src/tahlil.ts ni tahrirlaysiz, unda 42-qator boshqa joy. Xato xabari ham bir qatorli: nega? kim chaqirdi? CBU'ga ulanmagan bo'lsa — nega ulanmadi?

09-qismda brauzerdagi stack trace'ni o'qigansiz. Bu darsda Node'ga xos uch narsani qo'shamiz: async zanjir, build qilingan kod (source map) va xato sabablari zanjiri (cause). Shulardan keyin yuqoridagi xabar src\tahlil.ts:47:41 va src\cli.ts:66:18 ko'rinishida keladi.

O'xshatish: pochta posilkasi. Stack trace — posilka qaysi bo'limlardan o'tganini ko'rsatadigan muhrlar. Source map — "eski manzil → yangi manzil" jadvali (bo'lim ko'chgan bo'lsa ham to'g'ri joyni topasiz). cause — "qaytarildi, chunki: manzil topilmadi, chunki: uy raqami yo'q" zanjiri.

2. Stack trace'ni o'qish

2.1 Yuqoridan pastga

Uch funksiya bir-birini chaqiradi; eng ichkarisida xato. Blok Node uchun — o'z terminalingizda ishga tushiring. short yordamchisi uzun fayl yo'lini dars.mjs ga qisqartiradi (sizda fayl nomingiz chiqadi):

js
const short = (stack) =>
  stack.replace(/file:\/\/\/\S+?\.mjs/g, "dars.mjs").split("\n");

function unitRate(k) {
  if (!(k.nominal > 0)) {
    throw new RangeError(`Nominal noto'g'ri: ${k.nominal}`);
  }
  return k.kurs / k.nominal;
}
function convert(amount, from) {
  return amount * unitRate(from);
}
function main() {
  return convert(100, { kod: "IDR", kurs: 6.59, nominal: 0 });
}

try {
  main();
} catch (error) {
  console.log(short(error.stack).slice(0, 5).join("\n"));
}

Konsolda:

text
RangeError: Nominal noto'g'ri: 0
    at unitRate (dars.mjs:6:11)
    at convert (dars.mjs:11:19)
    at main (dars.mjs:14:10)
    at dars.mjs:18:3
Qator Ma'nosi
RangeError: … Xato turi va xabari
at unitRate (…:6:11) Xato shu yerda tashlandi: 6-qator, 11-ustun (throw)
at convert (…:11:19) unitRate ni chaqirgan joy
at main (…:14:10) convert ni chaqirgan joy
at dars.mjs:18:3 Modulning yuqori darajasi — main()

Qoida: birinchi at qatori — xato joyi, qolganlari — u yerga qanday kelinganining yo'li. Ko'pincha xato sababi birinchi qatorda emas, pastroqda: bu yerda unitRate to'g'ri ishladi, noto'g'ri nominal: 0 ni main bergan.

2.2 Node'ning o'z qatorlari

Haqiqiy chiqishda pastda yana qatorlar bo'ladi — node:internal/…:

text
    at ModuleJob.run (node:internal/modules/esm/module_job:561:25)
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Bu — Node'ning modul yuklovchisi. Sizning kodingiz emas, o'qishda o'tkazib yuboring. Faqat file:// yoki sizning papkangizdagi yo'l bilan boshlanganlariga qarang.

2.3 Async stack trace

await bilan chaqirilgan funksiyalar zanjiri saqlanadimi?

js
const short = (stack) =>
  stack.replace(/file:\/\/\/\S+?\.mjs/g, "dars.mjs").split("\n");
const wait = (ms) => new Promise((r) => setTimeout(r, ms));

async function fetchRate() {
  await wait(10);
  throw new Error("CBU javob bermadi");
}
async function showRate() {
  return await fetchRate();
}

try {
  await showRate();
} catch (error) {
  console.log(short(error.stack).slice(0, 4).join("\n"));
}

function oldStyle(callback) {
  setTimeout(() => callback(new Error("eski uslub")), 10);
}
oldStyle((error) => {
  console.log(short(error.stack).slice(0, 4).join("\n"));
});

Konsolda:

text
Error: CBU javob bermadi
    at fetchRate (dars.mjs:7:9)
    at async showRate (dars.mjs:10:10)
    at async dars.mjs:14:3
Error: eski uslub
    at Timeout._onTimeout (dars.mjs:20:29)
    at listOnTimeout (node:internal/timers:685:17)
    at process.processTimers (node:internal/timers:618:7)

Birinchi xato await wait(10) dan keyin tashlandi — event loop bir aylanib qaytdi. Shunga qaramay zanjir to'liq: at async showRate, at async dars.mjs. V8 buni async stack trace deb ataydi: await lar zanjirini eslab qoladi.

Ikkinchisi — eski callback uslubi. Stack Timeout._onTimeout dan boshlanadi va oldStyle ni kim chaqirgani yo'q. Taymer ishga tushganda asl chaqiruvchi allaqachon tugagan. Bu — callback'lardan async/await ga o'tishning yana bir sababi.

Tekshirib ko'ring: async function showRate() { return fetchRate(); } — await siz. Stackda at async showRate qatori bo'ladimi?

Javob

Yo'q bo'lishi mumkin. V8 async zanjirni await nuqtalari orqali tiklaydi. return fetchRate() da showRate Promise'ni darhol qaytaradi va zanjirdan chiqib ketadi — xato tashlanganda u "kutayotgan" funksiya emas. Shuning uchun ko'p qo'llanmalar return await ni try ichida va stack muhim joyda tavsiya qiladi. O'zingiz yuqoridagi blokda await ni olib tashlab tekshiring.

3. Stackni boshqarish

3.1 Error.captureStackTrace va stackTraceLimit

js
class UsageError extends Error {
  constructor(message) {
    super(message);
    this.name = "UsageError";
    Error.captureStackTrace(this, UsageError);
  }
}
function parseAmount(text) {
  const n = Number(text);
  if (Number.isNaN(n)) {
    throw new UsageError(`Miqdor son emas: ${text}`);
  }
  return n;
}
try {
  parseAmount("yuz");
} catch (error) {
  const lines = error.stack.split("\n");
  console.log(lines[0]);
  console.log(lines[1].trim().split(" ")[1]);
}

Error.stackTraceLimit = 1;
function a() { b(); }
function b() { throw new Error("chuqur"); }
try { a(); } catch (e) { console.log(e.stack.split("\n").length); }

Konsolda:

text
UsageError: Miqdor son emas: yuz
parseAmount
2
  • Error.captureStackTrace(obj, fn) — obj.stack ni hozirgi joydan yozadi va fn dan yuqoridagi qatorlarni (bu yerda konstruktorning o'zini) yashiradi. Natijada birinchi at qatori — parseAmount, ya'ni xato haqiqatan qayerda aniqlangan joy. Kutubxonalar o'z ichki funksiyalarini stackdan shunday olib tashlaydi.
  • Error.stackTraceLimit = 1 — nechta at qatori yig'iladi (sukut — 10). Natija 2 qator: xabar + 1 qator.

Bayroq bilan ham — butun jarayon uchun. Biz ishga tushirdik (3 chuqurlikdagi xato):

bash
node --stack-trace-limit=2 lim.mjs
text
Error: chuqur
    at c (file:///C:/Users/…/lim.mjs:3:22)
    at b (file:///C:/Users/…/lim.mjs:2:16)

Bayroqsiz shu faylda 7 ta at qatori chiqdi (Node ichkilari bilan). Chuqur rekursiya yoki ko'p qatlamli framework'da 10 kam bo'lishi mumkin — --stack-trace-limit=50. Hujjatga ko'ra 0 — stack umuman yig'ilmaydi.

4. Source map: dist/ dan src/ ga

4.1 Muammo

16-qismda source map'ni brauzer uchun ko'rgansiz: .map fayli "yig'ilgan koddagi qator → manbadagi qator" jadvali. valyuta ning tsconfig.build.json ida "sourceMap": true bor — dist/tahlil.js yonida dist/tahlil.js.map:

text
{"version":3,"file":"tahlil.js","sourceRoot":"","sources":["../src/tahlil.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU…

Bu — biz yasagan kichik misolning .map fayli boshi. sources — manba fayl, mappings — qator-ustun moslik jadvali (VLQ kodlashda). Brauzer DevTools uni o'zi o'qiydi. Node esa sukut bo'yicha o'qimaydi.

4.2 Ikki yo'l

Yo'l Qachon
node --enable-source-maps dist/bin.js Bayroq — ishga tushirgan odam beradi
module.setSourceMapsSupport(true) Kod ichidan — CLI foydalanuvchisi bayroq bermaydi

valyuta — npm'dan o'rnatiladigan CLI. Foydalanuvchi npx valyuta yozadi, bayroq yozmaydi. Shuning uchun kanon ikkinchi yo'lni tanladi.

4.3 Kanon topilmasi: statik importlar oldin baholanadi

Kanon birinchi variantda bin.ts boshiga yozdi:

ts
import { setSourceMapsSupport } from "node:module";
import { main } from "./cli.ts";

setSourceMapsSupport(true);
process.exitCode = await main(process.argv.slice(2), process.env);

Mantiq oddiy tuyuladi: avval yoqamiz, keyin ishlaymiz. Natija:

text
VALYUTA 10036: Error: 1-qator "sana,kod,nomi,nominal,kurs,farq" bo'lishi kerak, topildi: "yomon,sarlavha"
    at tahlilQil (file:///C:/Users/…/valyuta/dist/tahlil.js:42:23)
    at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
    at async tahlilBuyrugi (file:///C:/Users/…/valyuta/dist/cli.js:62:20)
    at async main (file:///C:/Users/…/valyuta/dist/cli.js:126:20)

Hamon dist/*.js. Sabab — ES Modules darsidagi qoida: statik import lar modul kodi ishlashidan oldin yuklanadi va baholanadi. cli.ts, tahlil.ts va boshqalar setSourceMapsSupport(true) qatoriga yetmasdan oldin yuklangan. Source map esa modul yuklanayotganda o'qiladi — kech qoldi.

Node 24 hujjati ham buni tasdiqlaydi: source map faqat yoqilgandan keyin yuklangan fayllar uchun o'qiladi. Shuning uchun hujjat iloji bo'lsa --enable-source-maps bayrog'ini tavsiya qiladi.

sequenceDiagram
  participant N as Node
  participant B as bin.js
  participant C as cli.js, tahlil.js
  N->>C: 1. statik import'lar yuklanadi
  N->>B: 2. bin.js kodi ishlaydi
  B->>B: setSourceMapsSupport(true)
  Note over C: map o'qilmadi —<br/>allaqachon yuklangan
  B->>C: 3. await import() — yangi modul
  Note over C: endi map o'qiladi

Yuqoridagi strelkalar — Node'ning haqiqiy tartibi: kodingiz yozilgan tartib emas. Tuzatish (kanon, holat 46):

ts
import { setSourceMapsSupport } from "node:module";

// Source map modul YUKLANAYOTGANDA o'qiladi — shuning uchun
// cli.ts statik import emas, yoqilgandan KEYIN dinamik import
setSourceMapsSupport(true);
const { main } = await import("./cli.ts");

Endi:

text
VALYUTA 9084: Error: 1-qator "sana,kod,nomi,nominal,kurs,farq" bo'lishi kerak, topildi: "yomon,sarlavha"
    at tahlilQil (C:\Users\…\valyuta\src\tahlil.ts:47:41)
    at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
    at async tahlilBuyrugi (C:\Users\…\valyuta\src\cli.ts:66:18)
    at async main (C:\Users\…\valyuta\src\cli.ts:129:39)

src\tahlil.ts:47:41 — manbadagi aniq joy. tsc ning rewriteRelativeImportExtensions dinamik import("./cli.ts") ni ham ./cli.js ga almashtirdi — build'da hech narsa buzilmadi. VALYUTA 10036 va 9084 — debuglog prefiksi va jarayon raqami (PID).

4.4 Biz takrorladik

Kanon topilmasini kichik misolda qaytadan sinadik: src/tahlil.ts (xato tashlaydi) va ikki xil bin, tsc --sourceMap bilan build:

text
== statik import + setSourceMapsSupport
    at qatorniOqi (file:///C:/Users/…/t46/dist/tahlil.js:4:15)
== dinamik import + setSourceMapsSupport
    at qatorniOqi (C:\Users\…\t46\src\tahlil.ts:9:11)
== statik import + node --enable-source-maps
    at qatorniOqi (C:\Users\…\t46\src\tahlil.ts:9:11)

Uchinchi qator muhim: bayroq bilan statik import ham ishladi — --enable-source-maps Node ishga tushishidanoq yoqiladi, hech qanday modul undan oldin yuklanmaydi. Muammo faqat kod ichidan yoqishda.

4.5 Narxi va chegarasi

  • Vaqt: kanonda valyuta --help source map bilan 92.2 ms, usiz (main() to'g'ridan) 87.7 ms — +4.5 ms (15 marta, mediana). Hujjat ham ogohlantiradi: Error.stack o'qilganda kechikish bo'lishi mumkin.
  • src/ yo'q: npm paketiga src/ kirmaydi (files: ["dist", "fixtures"]). Qator raqamlari to'g'ri, lekin src\tahlil.ts faylining o'zi foydalanuvchi kompyuterida yo'q. inlineSources: true bilan manba .map ichiga yoziladi — paket kattalashadi. Kanonda bu sozlama yo'q.
  • node_modules ichida: npm bilan o'rnatilgan paket node_modules papkasida yashaydi. Hujjatga ko'ra setSourceMapsSupport(true) u yerdagi fayllarni sukut bo'yicha o'tkazib yuboradi (nodeModules sozlamasi false). Biz kichik paket bilan sinadik (Node 24.21.0, Windows 11): setSourceMapsSupport(true) — stack node_modules\…\dist\lib.js ni ko'rsatdi; { nodeModules: true } bilan — …\src\lib.ts; --enable-source-maps bayrog'i bilan ham .ts. Kanon bin.ts da bu sozlama yo'q. Demak o'rnatilgan valyuta da stack dist/ ni ko'rsatadi — kanon buni o'rnatilgan holatda sinamagan. Amalda farq kichik: src/ baribir paketda yo'q.
  • .ts to'g'ridan: node src/bin.ts da source map umuman kerak emas — type stripping qator raqamlarini saqlaydi (biz ham tekshirdik: src/tahlil.ts:9:11).

5. error.cause: sabablar zanjiri

5.1 Nega

fetch darsida ko'rdingiz: tarmoq xatosi doim TypeError: fetch failed, asl sabab cause da. ES2022 dan har Error ga ikkinchi argument beriladi: new Error(xabar, { cause }). Xatoni ushlab, o'z tilingizda qayta tashlaysiz — lekin asl sababni yo'qotmaysiz.

js
import { inspect } from "node:util";

const network = new Error("connect ECONNREFUSED 127.0.0.1:59999");
const fetchError = new TypeError("fetch failed", { cause: network });
const apiError = new Error("CBU API: tarmoq xatosi", {
  cause: fetchError,
});

function causeChain(error, depth = 5) {
  const parts = [];
  for (let e = error; e !== undefined && parts.length < depth; ) {
    parts.push(e instanceof Error ? e.message : inspect(e));
    e = e instanceof Error ? e.cause : undefined;
  }
  return parts.join(" ← ");
}
console.log(causeChain(apiError));
console.log(causeChain(new Error("x", { cause: { kod: 1 } })));
console.log(apiError.cause === fetchError, "cause" in new Error("y"));

Konsolda:

text
CBU API: tarmoq xatosi ← fetch failed ← connect ECONNREFUSED 127.0.0.1:59999
x ← { kod: 1 }
true false
  • cause — istalgan qiymat bo'lishi mumkin (ikkinchi qatorda — oddiy obyekt). Shuning uchun inspect bilan matnga aylantiramiz.
  • depth — cheksiz zanjirdan himoya (a.cause = a kabi).
  • "cause" in new Error("y") — false: cause berilmasa, xususiyat umuman yo'q.

Bu — valyuta ning sababZanjiri funksiyasining o'zi (src/xato.ts, kanon).

5.2 Kanonda ko'rindi

valyuta ning ogohlantirishlari endi asl sababni aytadi (kanon 46-xato.txt):

text
Ogohlantirish: API ishlamadi (CBU API: tarmoq xatosi ← fetch failed ← connect ECONNREFUSED 127.0.0.1:59999) — saqlangan namuna kurslar (fixtures/) ishlatildi
Ogohlantirish: API ishlamadi (CBU API: tarmoq xatosi ← fetch failed ← bad port) — saqlangan namuna kurslar (fixtures/) ishlatildi
Ogohlantirish: API ishlamadi (CBU API: tarmoq xatosi ← fetch failed ← getaddrinfo ENOTFOUND yoq.invalid) — saqlangan namuna kurslar (fixtures/) ishlatildi

Holat 24 da hammasi bir xil edi: "CBU API: tarmoq xatosi". Endi uch xil holat uch xil ko'rinadi. 9-port fetch uchun "bloklangan port" ekanini net, dns va tls darsida ko'rgan edik — endi error.cause zanjiri buni kodning o'zida, foydalanuvchi xabarida ko'rsatadi.

console.log(xato) yoki ushlanmagan xatoda Node cause ni o'zi ham chiqaradi — [cause]: … bloki bilan (fetch darsidagi invalid onRequestStart method chiqishini eslang).

5.3 AggregateError: bir nechta xato birdan

Ba'zan sabab bitta emas. Promise.any — "birinchi muvaffaqiyatli" ni qaytaradi; hammasi yiqilsa — AggregateError:

js
const mirrors = [
  Promise.reject(new Error("cbu.uz: timeout")),
  Promise.reject(new Error("zaxira: 503")),
];
try {
  await Promise.any(mirrors);
} catch (error) {
  console.log(error.name, "|", error.message);
  for (const e of error.errors) console.log(" -", e.message);
}
const both = [new Error("a"), new Error("b")];
const agg = new AggregateError(both, "2 ta xato");
console.log(agg.errors.length, agg instanceof Error);

Konsolda:

text
AggregateError | All promises were rejected
 - cbu.uz: timeout
 - zaxira: 503
2 true

"Hamma Promise'lar rad etildi". Har biri errors massivida. Bir nechta faylni tekshiradigan validator yoki bir nechta zaxira API'si — AggregateError ning tabiiy joyi.

Tekshirib ko'ring: catch (e) { throw new Error("Kesh o'qilmadi"); } — bu qatorning muammosi nima va qanday tuzatasiz?

Javob

Asl xato (EACCES? SyntaxError? ENOENT?) yo'qoladi — log'da faqat "Kesh o'qilmadi" qoladi va sababini hech kim bilmaydi. Tuzatish: throw new Error("Kesh o'qilmadi", { cause: e }); — stack va xabar saqlanadi, sababZanjiri uni to'liq ko'rsatadi.

6. CLI qadami: holat 46 va commit

Holat 46 uch darsning yakuni: fetch (User-Agent, signal), debug (NODE_DEBUG da to'liq stack) va bugungi qism. Bugun qo'shilganlar:

Fayl O'zgarish
src/xato.ts Yangi: sababZanjiri (≤ 5), bekorQilinganmi (AbortError)
src/bin.ts setSourceMapsSupport(true) + dinamik import("./cli.ts"); Ctrl+C → AbortController
src/cli.ts Xato: sababZanjiri; debug("%s", xato.stack); AbortError → exit 130
src/manba.ts Ogohlantirishlarda sababZanjiri
testlar +2: zanjir va User-Agent; bekor qilingan signal → 130

bin.ts dagi Ctrl+C mantiqi: birinchi Ctrl+C — bekor.abort() (kanon kodiga ko'ra so'rov, kutish, oqim to'xtaydi, vaqtinchalik fayllar tozalanadi; haqiqiy terminalda Ctrl+C sinalmagan), ikkinchisi — darhol process.exit(130). Halol eslatma: haqiqiy Ctrl+C sinalmagan — Windows'da boshqa jarayondan yuborilgan SIGINT ishlovchini chaqirmaydi (process darsi), agentda esa konsol yo'q. Test buni modellaydi: main() ga 100 ms da bekor qilinadigan signal beriladi, 3 s kechikadigan server bilan — natija 130.

Yana bir halol eslatma: ikkinchi Ctrl+C dagi process.exit(130) hech narsani kutmaydi. Agar u tarix faylga yozayotgan paytga to'g'ri kelsa, vaqtinchalik .tmp fayl diskda qolishi mumkin (Xavfsiz fayl operatsiyalari darsidagi temp + rename). Asosiy fayl buzilmaydi, faqat chiqindi qoladi. Kanon bu holatni sinamagan.

Tekshiring:

bash
npm run check
npm run build
NODE_DEBUG=valyuta node dist/bin.js tahlil yomon.csv

Oxirgisida stack src\…\tahlil.ts ni ko'rsatishi kerak. Kanon natijasi (holat 46): build 1.9 s, tip 0, lint 0, testlar 41/41; pack 56 fayl, 27.4 kB; sovuq start 93/96 ms.

Commit (kanon):

bash
git add -A
git commit -m "Ctrl+C so'rov va oqimni bekor qiladi, xato sababi zanjiri, source map, User-Agent"

7. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Stack dist/*.js ni ko'rsatadi Source map yoqilmagan --enable-source-maps yoki setSourceMapsSupport
setSourceMapsSupport bor, baribir dist Statik importlar oldin yuklangan Keyin dinamik import()
Stack callback'dan boshlanadi, chaqiruvchi yo'q setTimeout/callback zanjirni uzadi async/await
at async … yo'q return await siz return await
Stack 10 qatorda uzildi Error.stackTraceLimit sukut 10 --stack-trace-limit=50
Log'da faqat fetch failed cause chiqarilmagan sababZanjiri / util.inspect
Qayta tashlangan xatoda asl sabab yo'q { cause } berilmagan new Error(x, { cause: e })
Stack chalkash: MyError konstruktori birinchi Konstruktor stackda Error.captureStackTrace(this, MyError)

8. Mashqlar

Yechimlar kurs/mashqlar/24/46-stack-cause/ da.

1-mashq (oson): stackni o'qing

text
TypeError: Cannot read properties of undefined (reading 'kurs')
    at birlikKursi (file:///…/src/kurs.ts:49:12)
    at konvert (file:///…/src/kurs.ts:53:22)
    at bajar (file:///…/src/buyruq.ts:31:18)
    at main (file:///…/src/cli.ts:120:11)
  • Xato tashlangan fayl va qator — kurs.ts:
  • birlikKursi ni chaqirgan funksiya —
  • Eng tashqi (birinchi chaqirilgan) funksiya —
  • birlikKursi ga nima keldi?
Yechim
  • 49 — birinchi at qatori.
  • konvert — ikkinchi qator.
  • main — oxirgi qator.
  • undefined — (reading 'kurs'): undefined.kurs. Sababini pastroqda — konvert ga noto'g'ri valyuta (topilmagan kod) bergan bajar da qidiring.

2-mashq (o'rta): wrap yordamchisi

wrap(message, fn) funksiyasini yozing: fn() ni chaqiradi, xato bo'lsa new Error(message, { cause }) bilan qayta tashlaydi; fn async bo'lsa ham ishlasin. Ikki qatlamli zanjir quring va causeChain (darsdagidek) bilan chiqaring.

Yechim
js
async function wrap(message, fn) {
  try {
    return await fn();
  } catch (cause) {
    throw new Error(message, { cause });
  }
}

function causeChain(error) {
  const parts = [];
  let e = error;
  while (e instanceof Error && parts.length < 5) {
    parts.push(e.message);
    e = e.cause;
  }
  return parts.join(" ← ");
}

const readCache = () => JSON.parse("{buzilgan");
try {
  await wrap("Kurs olinmadi", () =>
    wrap("Kesh o'qilmadi", async () => readCache()));
} catch (error) {
  console.log(causeChain(error));
}
console.log(await wrap("kerak emas", async () => 11903.15));

Konsolda:

text
Kurs olinmadi ← Kesh o'qilmadi ← Expected property name or '}' in JSON at position 1 (line 1 column 2)
11903.15

return await fn() — await siz try/catch async xatoni ushlamasdi: Promise try blokidan chiqib ketgan bo'lardi. Muvaffaqiyatli holatda wrap qiymatni o'zgarishsiz qaytaradi.

3-mashq (qiyin): stack chiqishini tozalash

Stack matnini foydalanuvchiga ko'rsatish uchun tozalaydigan cleanStack(stack, root) yozing. U uch ish qilsin: (1) node:internal qatorlarini olib tashlaydi; (2) root papka yo'lini . ga almashtiradi (Windows va URL ko'rinishidagi ikkala yo'l uchun); (3) bir xil qatorlar ketma-ket takrorlansa (rekursiya), ularni … (N marta takrorlandi) bilan qisqartiradi.

Yechim
js
function cleanStack(stack, root) {
  const rootUrl = "file:///" + root.replaceAll("\\", "/");
  const lines = stack
    .split("\n")
    .filter((l) => !l.includes("node:internal"))
    .map((l) => l.replaceAll(rootUrl, ".").replaceAll(root, "."));
  const out = [];
  for (let i = 0; i < lines.length; ) {
    let j = i;
    while (j + 1 < lines.length && lines[j + 1] === lines[i]) j++;
    out.push(lines[i]);
    if (j > i) out.push(`    … (${j - i} marta takrorlandi)`);
    i = j + 1;
  }
  return out.join("\n");
}

const root = "C:\\loyiha\\valyuta";
const stack = [
  "RangeError: Maximum call stack size exceeded",
  "    at kunlar (file:///C:/loyiha/valyuta/src/tarix.ts:12:3)",
  "    at kunlar (file:///C:/loyiha/valyuta/src/tarix.ts:12:3)",
  "    at kunlar (file:///C:/loyiha/valyuta/src/tarix.ts:12:3)",
  "    at main (C:\\loyiha\\valyuta\\src\\cli.ts:120:11)",
  "    at ModuleJob.run (node:internal/modules/esm/module_job:561)",
].join("\n");
console.log(cleanStack(stack, root));

Konsolda:

text
RangeError: Maximum call stack size exceeded
    at kunlar (./src/tarix.ts:12:3)
    … (2 marta takrorlandi)
    at main (.\src\cli.ts:120:11)

Source map yoqilgan Windows yo'li (C:\…) va yoqilmagan URL (file:///C:/…) — bitta stackda ikkalasi uchrashi mumkin (kanondagi "oldin/keyin" chiqishlarini solishtiring), shuning uchun ikkalasi ham almashtiriladi.

4-mashq: CLI qadami

src/xato.ts ni yarating (sababZanjiri, bekorQilinganmi), manba.ts va cli.ts dagi xabarlarni zanjirga o'tkazing. bin.ts da setSourceMapsSupport(true) va dinamik import("./cli.ts"). Tekshiring: (1) avval statik import bilan npm run build + NODE_DEBUG=valyuta node dist/bin.js tahlil yomon.csv — stack dist/ ni ko'rsatadi; (2) dinamik import bilan — src\…\.ts. npm run check toza bo'lsa, holat 46 ni commit qiling.

Yechim

Kod — «CLI qadami» jadvali va «Source map» bo'limidagi kanon bin.ts. yomon.csv — birinchi qatori yomon,sarlavha bo'lgan istalgan fayl. (1) va (2) natijalari — kanon chiqishlari (dist/tahlil.js:42:23 → src\tahlil.ts:47:41). Commit xabari — «CLI qadami» bo'limidagi kanon xabari.

9. Real ishda

  • Xato kuzatish xizmatlari (Sentry va boshqalar — 21-qismda frontend uchun) serverda ham source map bilan ishlaydi: prod'da dist, hisobotda — src qatorlari.
  • Log'lar — cause zanjirini JSON log'ga yozish (err.cause ni ham serializatsiya qilish) — tunda yiqilgan serverni ertalab tushunishning yagona yo'li.
  • Kutubxona mualliflari Error.captureStackTrace va o'z xato klasslari bilan foydalanuvchiga keraksiz ichki qatorlarni ko'rsatmaydi.
  • Intervyu savollari: "Stack trace'ni qanday o'qiysiz?", "error.cause nima uchun?", "Node'da TypeScript xato qatorlarini qanday to'g'ri ko'rsatasiz?".

Xulosa

  • Stack: birinchi at — xato joyi, qolgani — u yerga yo'l; node:internal qatorlarini o'tkazib yuboring.
  • await zanjiri at async … bilan saqlanadi; setTimeout/callback zanjirni uzadi; return await muhim.
  • Error.captureStackTrace(this, Klass) — keraksiz qatorlarni yashiradi; stackTraceLimit sukut 10, --stack-trace-limit bilan o'zgaradi.
  • Source map: --enable-source-maps yoki setSourceMapsSupport(true) — ikkinchisi statik importlardan keyin ishlamaydi, kodni dinamik import() bilan yuklang (kanon topilmasi, +4.5 ms).
  • new Error(x, { cause }) — asl sababni saqlang; zanjirni chiqaring (A ← B ← C); bir nechta sabab — AggregateError.

Keyingi dars: CPU profiling va flamegraph — sekin kodni taxmin bilan emas, --cpu-prof profili bilan topish: valyuta tahlil vaqtining 22 % i qayerga ketadi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Node.js stack trace, source map va error.cause: xato xabarini to'g'ri o'qish — IlmHamroh