IlmHamroh
JavaScript Full-stack/15-qism. TypeScript44/47-dars21 daqiqa
Mundarija (30)

TypeScript'da async kod va xatolar: Promise<T>, catch (e: unknown) va Result<T, E>

Qisqacha: async funksiya doim Promise<T> qaytaradi — natija turini Promise<number> deb yozasiz, number emas. await unutilsa, tsc ko'pincha ushlaydi (Promise da length yo'q). Hammasini emas: kutilmagan Promise'ni ESLint'ning no-floating-promises qoidasi topadi. catch (e) dagi e — unknown: JavaScript'da istalgan narsani tashlash mumkin, shuning uchun avval e instanceof Error bilan tekshirasiz. Kutilgan xatolar uchun esa Result<T, E> — ok maydonli union: xatoni tashlash o'rniga qiymat qilib qaytarasiz va tsc uni tekshirishni majbur qiladi.

Bu darsda

  • async funksiyaga natija turini yozasiz, Awaited va Promise.all turlarini o'qiysiz.
  • Unutilgan await ni tsc va ESLint qanday topishini bilasiz.
  • catch dagi unknown ni instanceof Error bilan toraytirasiz va .catch((e) => …) dagi any tuzog'ini taniysiz.
  • O'z xato klassingizni turli kod bilan yozasiz (cause bilan).
  • Result<T, E> turini yozasiz va qachon throw, qachon Result tanlashni bilasiz.

Oldin bilishingiz kerak: async / await, O'z xato klasslaringiz, Xato strategiyasi, Zod va turni sxemadan chiqarish, Discriminated union.

1. Nega bu kerak?

O'tgan ikki darsda server javobini tekshirdik. Lekin tekshiruvdan oldin so'rovning o'zi muvaffaqiyatsiz bo'lishi mumkin: internet uzildi, server 500 berdi, foydalanuvchi so'rovni bekor qildi. 11-qismda bularning hammasini try/catch bilan ushlagan edik (Xato strategiyasi).

TypeScript'da bitta yangi to'siq paydo bo'ladi. Sardor «Bahor» buyurtmasini yuborish kodini .ts ga ko'chirdi:

ts
async function sendOrder(): Promise<number> {
  throw new Error("Server javob bermadi");
}

try {
  await sendOrder();
} catch (error) {
  console.log(error.message);
}

Kompilyator:

text
TS18046: 'error' is of type 'unknown'.

TypeScript aytyapti: "error ning turi — unknown" (noma'lum).

JavaScript'da ishlab turgan oddiy error.message endi xato! Sabab — TypeScript catch ga nima kelishini bilmaydi. Bugun shuni va async kodning boshqa tur tuzoqlarini ko'ramiz.

Hayotiy o'xshatish: oshxonaga "muammo bor" degan xabar keldi. Bu gaz o'chganimi, ziravor tugaganimi yoki mijoz shikoyatimi — bilmaysiz. Darhol "gaz ustasini chaqir" deb buyruq bermaysiz — avval nima bo'lganini aniqlaysiz. unknown ham shunday: avval tekshir, keyin harakat qil.

2. Promise<T> va async funksiya turlari

2.1 Natija turi — doim Promise

async funksiya nima qaytarsa ham, chaqiruvchi Promise oladi (async / await). Natija turi ham shuni aytishi kerak:

ts
async function loadPrice(): number {
  return 35000;
}

Kompilyator:

text
TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'?

Tarjimasi: "async funksiya yoki metodning natija turi global Promise<T> bo'lishi shart. Promise<number> demoqchimidingiz?" Xabar tuzatishni o'zi taklif qildi. To'g'risi:

ts
async function loadPrice(): Promise<number> {
  return 35000;
}

const price = await loadPrice();
console.log(price + 5000); // 40000

loadPrice() — Promise<number>, await loadPrice() — number. Natija turini yozmasangiz, TypeScript uni chiqaradi (tur chiqarish), lekin export qilinadigan async funksiyaga yozish yaxshi odat: imzodan "bu funksiya kutiladi" degani ko'rinib turadi.

2.2 Awaited va Promise.all

Ikkita foydali tur:

ts
async function getMenu(): Promise<string[]> {
  return ["Osh", "Manti"];
}
async function isOpen(): Promise<boolean> {
  return true;
}

type Menu = Awaited<ReturnType<typeof getMenu>>; // string[]

const [menu, open] = await Promise.all([getMenu(), isOpen()]);
const first: Menu[number] = menu[0] ?? "—";
console.log(first, open); // Osh true
  • Awaited<T> — utility type: "Promise ichidagi tur". ReturnType<typeof getMenu> — Promise<string[]>, Awaited<…> — string[]. Funksiya natijasining turini qayta yozmasdan olish usuli.
  • Promise.all([…]) natijasi — tuple (Massiv va tuple): [string[], boolean]. Har o'rin o'z turini saqlaydi: menu — string[], open — boolean.

2.3 Unutilgan await

Async kodning eng ko'p uchraydigan xatosi — await ni yozmaslik. TypeScript ularning ko'pini ushlaydi:

ts
async function getMenu(): Promise<string[]> {
  return ["Osh", "Manti"];
}
async function isOpen(): Promise<boolean> {
  return true;
}

const menu = getMenu();
console.log(menu.length);
if (isOpen()) {
  console.log("Oshxona ochiq");
}

Kompilyator:

text
TS2339: Property 'length' does not exist on type 'Promise<string[]>'.
TS2801: This condition will always return true since this 'Promise<boolean>' is always defined.
  • TS2339: menu — massiv emas, Promise. Unda length yo'q.
  • TS2801: "bu shart doim true: Promise<boolean> doim mavjud". Promise obyekti — har doim "truthy" qiymat, natijasi false bo'lsa ham. JavaScript'da bu jim xato bo'lardi: oshxona yopiq bo'lsa ham "ochiq" chiqardi.

Lekin bitta holatni tsc ko'rmaydi: natijasi umuman ishlatilmagan Promise.

ts
async function saveOrder(): Promise<void> { /* … server … */ }

function onSubmit(): void {
  saveOrder(); // kutilmadi — xato bo'lsa, hech kim ushlamaydi
}

Bu qator turlar nuqtai nazaridan to'g'ri. Lekin saveOrder rad etilsa (reject), xato hech qayerda ushlanmaydi: brauzer konsolida Uncaught (in promise) (Ushlanmagan xatolar). Buni typescript-eslint'ning no-floating-promises qoidasi topadi — u TypeScript turlarini o'qiydi va "Promise qaytdi, lekin na await, na .catch, na void" degan joylarni ko'rsatadi. «Vazifalar qadami» da bu qoida vazifalar da 11 ta joy topdi.

Tekshirib ko'ring: const total = calcTotal(); — calcTotal async. Keyin total.toFixed(0) yozildi. tsc nima deydi?

Javob

TS2339: Property 'toFixed' does not exist on type 'Promise<number>'. — total Promise, son emas. Tuzatish: const total = await calcTotal();. JavaScript'da bu TypeError: total.toFixed is not a function bo'lardi — faqat ishlash paytida.

3. catch dagi unknown

3.1 Nega unknown

JavaScript'da throw ga istalgan qiymat berish mumkin: throw new Error("…"), throw "satr", throw 404, throw { kod: 1 }. Kutubxona ichidan nima kelishini bilmaysiz. Shuning uchun strict rejimda (useUnknownInCatchVariables bayrog'i, TypeScript 4.4 dan) catch (error) dagi error — unknown. «Nega bu kerak?» dagi TS18046 shu.

Tuzatish — toraytirish:

ts
async function sendOrder(): Promise<number> {
  throw new Error("Server javob bermadi");
}

try {
  await sendOrder();
} catch (error) {
  if (error instanceof Error) {
    console.log(error.message); // Server javob bermadi
  } else {
    console.log("Noma'lum xato:", String(error));
  }
}

Bu tekshiruv ko'p joyda takrorlanadi. Uni bitta funksiyaga chiqaramiz — vazifalar dagi xatoMatni:

ts
function errorMessage(error: unknown): string {
  return error instanceof Error ? error.message : String(error);
}

const negative = new RangeError("Narx manfiy");
console.log(errorMessage(negative)); // Narx manfiy
console.log(errorMessage("tarmoq yo'q")); // tarmoq yo'q
console.log(errorMessage(404)); // 404

error: unknown — "istalgan narsani ber" degan va'da, va funksiya uni haqiqatan bajaradi.

Diqqat: catch (error: Error) deb yozib bo'lmaydi — tsc faqat any yoki unknown ga ruxsat beradi (TS1196). catch (error: any) esa eski xulqni qaytaradi va tekshiruvni o'chiradi. Yozmang.

3.2 Tuzoq: .catch((e) => …) — any

try/catch dagi xato — unknown. Promise'ning .catch metodidagi esa — any. lib.es5.d.ts da u reason: any deb yozilgan va strict buni o'zgartirmaydi:

ts
const failed: Promise<string> = Promise.reject("tarmoq yo'q");
failed.catch((error) => {
  console.log("xato keldi");
  console.log(error.message.toUpperCase());
});

Konsolda:

text
xato keldi
TypeError: Cannot read properties of undefined (reading 'toUpperCase')

tsc — 0 xato. error — any, error.message — tekshiruvsiz. Lekin rad etish sababi — oddiy satr, uning message i yo'q: undefined.toUpperCase(). Eng yomoni — xato catch ning ichida chiqdi, ya'ni xatoni ushlovchi o'zi ushlanmagan xato tashladi.

Qoida: .catch da parametrga o'zingiz tur yozing: .catch((error: unknown) => …). typescript-eslint'da buni majburlaydigan qoida ham bor — use-unknown-in-catch-callback-variable.

3.3 O'z xato klassingiz — turli kod bilan

O'z xato klasslaringiz darsidagi ApiXatosi ni TypeScript'da yozamiz. Kod maydoni — literal union:

ts
type OrderErrorCode = "CLOSED" | "NETWORK" | "REJECTED";

class OrderError extends Error {
  readonly code: OrderErrorCode;
  constructor(
    code: OrderErrorCode,
    message: string,
    options?: ErrorOptions,
  ) {
    super(message, options);
    this.name = "OrderError";
    this.code = code;
  }
}

try {
  throw new OrderError("NETWORK", "Tarmoq xatosi", {
    cause: new TypeError("fetch failed"),
  });
} catch (error) {
  if (error instanceof OrderError && error.code === "NETWORK") {
    console.log(error.message, "←", String(error.cause));
  }
}

Konsolda:

text
Tarmoq xatosi ← TypeError: fetch failed
  • instanceof OrderError dan keyin error.code — OrderErrorCode. "NETWROK" deb xato yozsangiz, tsc darhol aytadi.
  • ErrorOptions — lib.es2022 dagi tur: { cause?: unknown }. cause — asl sabab, zanjirni yo'qotmaslik uchun (Xato strategiyasi).
  • error.cause ning turi ham unknown — tashlangan narsa kabi, sabab ham istalgan qiymat bo'lishi mumkin.

3.4 Bekor qilish — xato emas

AbortController bilan so'rovni bekor qilsangiz, Promise rad etiladi — catch ga xato keladi. Lekin foydalanuvchi o'zi "bekor" bosdi — unga "xato" ko'rsatish kerak emas. Bu holatni turlar bilan ajratamiz:

ts
function wait(ms: number, signal: AbortSignal): Promise<string> {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => resolve("tayyor"), ms);
    signal.addEventListener("abort", () => {
      clearTimeout(timer);
      reject(signal.reason);
    }, { once: true });
  });
}

function isAbort(error: unknown): boolean {
  return error instanceof DOMException && error.name === "AbortError";
}

const controller = new AbortController();
const pending = wait(1000, controller.signal);
controller.abort(); // foydalanuvchi "Bekor qilish" ni bosdi
try {
  await pending;
} catch (error) {
  const text = isAbort(error) ? "Bekor qilindi" : "Xato";
  console.log(text); // Bekor qilindi
}
  • signal.reason — any turida, lekin abort() argumentsiz chaqirilsa, u DOMException (name: "AbortError"). Node 24 da ham, brauzerda ham.
  • isAbort — unknown oladi va faqat bitta aniq holatni tan oladi. DOMException — Error ning bolasi, shuning uchun errorMessage ham u bilan ishlaydi.
  • fetch ham bekor qilinganda xuddi shunday AbortError bilan rad etiladi. vazifalar dagi ApiXatosi ning "BEKOR" kodi aynan shu holat.

Tekshirib ko'ring: Qidiruv maydonida har harf terilganda eski so'rov bekor qilinadi va yangisi yuboriladi (race condition darsidagi yechim). Agar catch da isAbort tekshiruvi bo'lmasa, foydalanuvchi nima ko'radi?

Javob

Har harfda "Xato" xabarini — chunki har bekor qilingan so'rov AbortError bilan rad etiladi. Bekor qilish — dastur o'zi tanlagan normal holat, uni jim o'tkazib yuborish kerak. Shuning uchun catch da birinchi tekshiruv odatda if (isAbort(error)) return;, keyin haqiqiy xatolar. TypeScript bu farqni o'zi ko'rmaydi: ikkalasi ham unknown — ajratish sizning kodingizda.

4. Result<T, E> — xato ham qiymat

4.1 Throw'ning tur tizimidagi teshigi

throw ning bitta katta kamchiligi bor: u funksiya imzosida ko'rinmaydi. async function sendOrder(): Promise<number> — imzo xato haqida hech narsa demaydi. try ni unutsangiz, tsc jim. Java kabi ba'zi tillarda "bu funksiya shu xatolarni tashlaydi" deb yoziladi, TypeScript'da esa bunday yozuv yo'q.

Yechim — kutilgan xatoni natijaning bir qismi qilish. API ma'lumotlarini tiplash dagi ApiResponse<T> ni umumlashtiramiz:

ts
type Result<T, E> =
  | { readonly ok: true; readonly value: T }
  | { readonly ok: false; readonly error: E };

function parsePrice(text: string): Result<number, string> {
  const value = Number(text.replaceAll(" ", ""));
  if (!Number.isInteger(value) || value < 0) {
    return { ok: false, error: `Narx noto'g'ri: "${text}"` };
  }
  return { ok: true, value };
}

const a = parsePrice("35 000");
const b = parsePrice("o'ttiz");
console.log(a.ok ? a.value : a.error); // 35000
console.log(b.ok ? b.value : b.error); // Narx noto'g'ri: "o'ttiz"

Endi imzo hamma narsani aytadi: Result<number, string> — "son, yoki satr ko'rinishidagi xato". Chaqiruvchi ok ni tekshirmasdan qiymatga yeta olmaydi:

ts
type Result<T, E> =
  | { readonly ok: true; readonly value: T }
  | { readonly ok: false; readonly error: E };

declare function parsePrice(text: string): Result<number, string>;
const price = parsePrice("35 000");
console.log(price.value + 5000);

Kompilyator:

text
TS2339: Property 'value' does not exist on type 'Result<number, string>'.
  Property 'value' does not exist on type '{ readonly ok: false; readonly error: string; }'.

Davomini o'qing: "ok: false holatida value yo'q". Discriminated union xatoni unutishga yo'l qo'ymadi. 12-qismdagi "temir yo'l" uslubi (FP uslubida xatolar) — xuddi shu g'oya, endi tur kafolati bilan.

4.2 Qachon Result, qachon throw

Hamma xatoni Result qilish shart emas. Chegara oddiy:

Holat Misol Usul
Kutilgan, foydalanuvchiga ko'rsatiladi buzuq import fayli, oshxona yopiq, 422 Result
Tarmoq yoki server muvaffaqiyatsiz oflayn, 500 Result (async)
Dasturchi xatosi (bug) undefined.x, kelishuv buzilgan throw
flowchart TB
  A["Xato yuz berdi"] --> B{"Kutilganmi?"}
  B -->|"ha: 404, oflayn"| C["Result<br/>ok: false"]
  B -->|"yo'q: bug"| D["throw"]
  C --> E["foydalanuvchiga xabar"]
  D --> F["konsolda iz,<br/>test qizil"]

Bug'ni Result qilib qaytarsangiz, u "kutilgan xato" kabi jim o'tib ketadi. Bug baland ovozda yiqilishi kerak: konsolda iz, testda qizil. vazifalar da aynan shu chegara: paketniTekshir buzuq matnni Result qiladi, boshqa har qanday xatoni esa yana tashlaydi.

4.3 Async Result

Async funksiya Promise<Result<T, E>> qaytaradi — Promise o'zi hech qachon rad etilmaydi (kutilgan xatolar uchun). 3-mashqda «Bahor» buyurtmasini shunday yozasiz:

ts
async function placeOrder(
  dishes: readonly string[],
  send: Send,
  now: Date,
): Promise<Result<number, OrderError>>

Chaqiruvchi:

ts
const result = await placeOrder(["Osh"], send, new Date());
if (!result.ok) {
  showError(result.error.message); // CLOSED, NETWORK, REJECTED
  return;
}
showSuccess(`Buyurtma №${result.value}`);

Bunday Promise'ni void placeOrder(…) bilan "kutmasdan" chaqirish xavfsiz — u rad etilmaydi. «Vazifalar qadami» dagi void lar aynan shunday.

Tekshirib ko'ring: JSON.parse buzuq matnda SyntaxError tashlaydi. Foydalanuvchi import maydoniga matn qo'yadi. Bu holat uchun throw yoki Result?

Javob

Result. Foydalanuvchi noto'g'ri matn qo'yishi — kutilgan, normal holat: unga "matn JSON emas" deb aytish kerak. paketniTekshir aynan shu: JSON.parse ning SyntaxError ini ushlab, { ok: false, xato } qaytaradi. Agar esa paketniOqi ichida dasturchi xatosi bo'lsa (masalan, undefined ga murojaat) — u Result'ga aylanmaydi, throw davom etadi.

5. Ko'p uchraydigan xatolar

5.1 TS18046: 'error' is of type 'unknown'.

catch ichida error.message. Tuzatish: error instanceof Error yoki errorMessage(error) yordamchisi.

5.2 TS1064 — async natija turi Promise emas

Tuzatish: Promise<T> — xabar o'zi taklif qiladi.

5.3 TS2339 / TS2801 — await unutilgan

Promise da length yo'q; if (promise) doim true. Tuzatish: await.

5.4 Kutilmagan Promise (tsc jim)

saveOrder(); — rad etilsa, ushlanmaydi. Tuzatish: await, .catch(…) yoki, Promise hech qachon rad etilmasa, void saveOrder() va izoh. ESLint: no-floating-promises.

5.5 .catch((e) => e.message) — any

Tuzatish: .catch((e: unknown) => …).

5.6 throw "satr"

Satr tashlansa, instanceof Error ishlamaydi, stack yo'q. Tuzatish: har doim Error (yoki uning bolasi). ESLint: only-throw-error.

6. Mashqlar

1-mashq (oson): Turlarni ayting

ts
async function countGuests(): Promise<number> { return 4; }
const a = countGuests();
const b = await countGuests();
const name = Promise.resolve("Malika");
const c = await Promise.all([countGuests(), name]);

b ning turi: [:number]. c[1] ning turi: [:string]. a ning turi Promise<…> — ichida: .

Yechim

a — Promise<number> (await yo'q). b — number. c — tuple [number, string], shuning uchun c[1] — string. Promise.all har o'rinning turini alohida saqlaydi.

2-mashq (o'rta): Xato matnini xavfsiz olish

describeError(error: unknown): string funksiyasini yozing:

  • Error bo'lsa — "<name>: <message>", cause bor bo'lsa oxiriga (sabab: <cause matni>);
  • satr bo'lsa — o'zi;
  • boshqa hamma narsa — "Noma'lum xato".

any va as ishlatmang. Ishora: cause ham unknown — uning matni uchun funksiyani o'zini chaqiring (rekursiya).

Yechim
ts
function describeError(error: unknown): string {
  if (error instanceof Error) {
    const base = `${error.name}: ${error.message}`;
    return error.cause === undefined
      ? base
      : `${base} (sabab: ${describeError(error.cause)})`;
  }
  if (typeof error === "string") {
    return error;
  }
  return "Noma'lum xato";
}

const network = new Error("Buyurtma yuborilmadi", {
  cause: new TypeError("fetch failed"),
});
console.log(describeError(network));
console.log(describeError("tarmoq yo'q")); // tarmoq yo'q
console.log(describeError({ kod: 500 })); // Noma'lum xato

Konsolda:

text
Error: Buyurtma yuborilmadi (sabab: TypeError: fetch failed)
tarmoq yo'q
Noma'lum xato

Har if dan keyin TypeScript error ni toraytiradi: instanceof Error ichida name, message, cause bor; typeof … === "string" ichida — satr. { kod: 500 } uchun aniq ma'lumot yo'q — String(error) "[object Object]" berardi, shuning uchun umumiy matn.

3-mashq (qiyin): Amaliy tajriba — buyurtma yuborish

kurs/mashqlar/15/44-async/order.ts:

  • Result<T, E> turi va OrderError klassi (code: "CLOSED" | "NETWORK" | "REJECTED", cause bilan).
  • placeOrder(dishes, send, now): Promise<Result<number, OrderError>>. send — (body: string) => Promise<Response> (testda soxta). Qoidalar: oshxona 07:00–23:00 ishlaydi (yopiq — CLOSED, so'rov yuborilmaydi); send rad etilsa — NETWORK (cause — asl xato); javob ok emas — REJECTED; javobda id son bo'lmasa — bu server bilan kelishuv buzilgani, throw.
  • order.test.ts — beshta holat uchun test.
Yechim
ts
// 44-async/order.ts — buyurtma yuborish: kutilgan xato — Result
export type Result<T, E> =
  | { readonly ok: true; readonly value: T }
  | { readonly ok: false; readonly error: E };

export type OrderErrorCode = "CLOSED" | "NETWORK" | "REJECTED";

export class OrderError extends Error {
  readonly code: OrderErrorCode;
  constructor(
    code: OrderErrorCode,
    message: string,
    options?: ErrorOptions,
  ) {
    super(message, options);
    this.name = "OrderError";
    this.code = code;
  }
}

export type Send = (body: string) => Promise<Response>;

// «Bahor» 07:00–23:00 ishlaydi
function isOpen(now: Date): boolean {
  const hour = now.getHours();
  return hour >= 7 && hour < 23;
}

function hasId(data: unknown): data is { id: number } {
  return (
    typeof data === "object" &&
    data !== null &&
    "id" in data &&
    typeof data.id === "number"
  );
}

export async function placeOrder(
  dishes: readonly string[],
  send: Send,
  now: Date,
): Promise<Result<number, OrderError>> {
  if (!isOpen(now)) {
    const message = "Oshxona yopiq (07:00–23:00)";
    return { ok: false, error: new OrderError("CLOSED", message) };
  }
  let response: Response;
  try {
    response = await send(JSON.stringify({ dishes }));
  } catch (cause) {
    return {
      ok: false,
      error: new OrderError("NETWORK", "Tarmoq xatosi", { cause }),
    };
  }
  if (!response.ok) {
    const message = `Server rad etdi: ${response.status}`;
    return { ok: false, error: new OrderError("REJECTED", message) };
  }
  const data: unknown = await response.json();
  if (!hasId(data)) {
    // server bilan kelishuv buzildi — bu bug, Result emas
    throw new TypeError("Server javobida id yo'q");
  }
  return { ok: true, value: data.id };
}
  • catch (cause) — unknown, va u to'g'ridan-to'g'ri { cause } ga ketadi: ErrorOptions.cause ham unknown. Toraytirish shart emas.
  • let response: Response; — try ichida qiymat oladi. tsc keyingi qatorlarda u albatta berilganini tushunadi: catch dan return bilan chiqiladi.
  • hasId — tur predikati: id yo'q javob — Result emas, throw. Bu "server bilan kelishuv buzildi" — kutilmagan holat.

44-async/order.test.ts:

ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { placeOrder, type Send } from "./order.ts";

const NOON = new Date(2026, 9, 6, 12, 0);
const reply = (body: string, status = 200): Send =>
  async () => new Response(body, { status });

test("muvaffaqiyat — buyurtma raqami", async () => {
  const result = await placeOrder(["Osh"], reply('{"id":15}'), NOON);
  assert.deepEqual(result, { ok: true, value: 15 });
});

test("kechasi — CLOSED, so'rov yuborilmaydi", async () => {
  let sent = false;
  const send: Send = async () => {
    sent = true;
    return new Response("{}");
  };
  const night = new Date(2026, 9, 6, 23, 30);
  const result = await placeOrder(["Osh"], send, night);
  assert.ok(!result.ok);
  assert.equal(result.error.code, "CLOSED");
  assert.equal(sent, false);
});

test("tarmoq uzildi — NETWORK, sabab saqlanadi", async () => {
  const offline: Send = async () => {
    throw new TypeError("fetch failed");
  };
  const result = await placeOrder(["Manti"], offline, NOON);
  assert.ok(!result.ok);
  assert.equal(result.error.code, "NETWORK");
  assert.ok(result.error.cause instanceof TypeError);
});

test("422 — REJECTED", async () => {
  const result = await placeOrder([], reply("{}", 422), NOON);
  assert.ok(!result.ok);
  assert.equal(result.error.message, "Server rad etdi: 422");
});

test("javobda id yo'q — dasturchi xatosi, throw", async () => {
  const pending = placeOrder(["Osh"], reply("{}"), NOON);
  await assert.rejects(pending, TypeError);
});

import { placeOrder, type Send } — bitta importda qiymat va tur (Modullar TypeScript'da, verbatimModuleSyntax uchun type). assert.ok(!result.ok) dan keyin result.error — assertion funksiya ok: false holatiga toraytirdi. new Date(2026, 9, 6, …) — oy 0 dan boshlanadi: 9 — oktabr. node --test 44-async/order.test.ts — bizda (Node 24.21.0):

text
✔ muvaffaqiyat — buyurtma raqami (64.3427ms)
✔ kechasi — CLOSED, so'rov yuborilmaydi (0.5442ms)
✔ tarmoq uzildi — NETWORK, sabab saqlanadi (0.4712ms)
✔ 422 — REJECTED (0.67ms)
✔ javobda id yo'q — dasturchi xatosi, throw (1.5304ms)
ℹ tests 5
ℹ suites 0
ℹ pass 5
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 336.9058

XATOLAR.md ga: TS18046 (catch), TS1064, TS2801, TS2339 (Result tekshirilmagan) va ".catch((e) => …) — any" tuzog'i.

bash
git add 15/44-async 15/XATOLAR.md
git commit -m "15/44: placeOrder — Result, OrderError va testlar"

4-mashq: Vazifalar qadami — Natija turi va no-floating-promises

vazifalar da import, kesh va boshqa tab xabari paketniOqi ni try/catch bilan chaqiradi. Uchta joyda bir xil naqsh: "buzuq matn — ogohlantirish, qolgan xato — ko'tarilsin". Endi bu Natija turiga o'tadi. Ikkinchi ish — ESLint'ga Promise qoidalarini qo'shish.

  • Branch: refactor/natija-turi
  • Commit: refactor: Natija (Result) turi, no-floating-promises, tanasi: paketniTekshir; void bilan kutilmaydigan Promise; olchov import catch
  1. turlar.ts ga Natija<T, E>, paket.ts ga paketniTekshir(matn): Natija<VazifaMalumoti[], VazifaXatosi>.
  2. saqlash.ts (yukla), asosiy.ts (ozgargan, importQil) — try/catch o'rniga Natija.
  3. eslint.config.js ga turli qoidalar: no-floating-promises, no-misused-promises, only-throw-error. npm run lint topganlarini tuzating.
  4. tekshiruv/paket.test.js ga 2 test.
Yechim

1-qadam. turlar.ts:

ts
/**
 * Natija (Result): kutilgan xato ham qiymat — throw emas. `ok`
 * bo'yicha discriminated union: true bo'lsa `qiymat`, false bo'lsa
 * `xato` bor; ikkalasini tekshirmasdan olib bo'lmaydi
 */
export type Natija<T, E> =
  | { readonly ok: true; readonly qiymat: T }
  | { readonly ok: false; readonly xato: E };

paket.ts — paketniOqi qoladi (u throw qiladi), uning ustiga throw'siz shakl:

ts
/**
 * paketniOqi ning throw'siz shakli. Kutilgan xato (buzuq matn —
 * VazifaXatosi) — natija qiymati: chaqiruvchi ok ni tekshirmasdan
 * vazifalarni ololmaydi. Boshqa xato — dasturchi xatosi, throw qoladi
 */
export function paketniTekshir(
  matn: string,
): Natija<VazifaMalumoti[], VazifaXatosi> {
  try {
    return { ok: true, qiymat: paketniOqi(matn) };
  } catch (xato) {
    if (xato instanceof VazifaXatosi) {
      return { ok: false, xato };
    }
    throw xato;
  }
}

catch (xato) — unknown; instanceof VazifaXatosi faqat kutilgan xatoni Natija'ga aylantiradi. Qolgani — throw xato bilan yuqoriga.

2-qadam. saqlash.ts — oldin va keyin:

ts
// Oldin
try {
  return paketniOqi(matn);
} catch (xato) {
  console.warn(`Saqlangan vazifalar buzuq: ${xatoMatni(xato)}`);
  return null;
}

// Keyin
const natija = paketniTekshir(matn);
if (!natija.ok) {
  console.warn(`Saqlangan vazifalar buzuq: ${natija.xato.message}`);
  return null;
}
return natija.qiymat;

natija.xato — VazifaXatosi, .message ga toraytirishsiz murojaat qilinadi. xatoMatni yordamchisi saqlash.ts da kerak bo'lmay qoldi. ok tekshirilmasa — haqiqiy tsc xabari:

text
error TS2339: Property 'qiymat' does not exist on type 'Natija<VazifaMalumoti[], VazifaXatosi>'.

asosiy.ts dagi importQil da esa try qoladi — faqat dasturchi xatosi uchun: foydalanuvchiga "Kutilmagan xato: import bajarilmadi." deyiladi va throw davom etadi (konsolda iz qolsin). Kutilgan xato — Natija, bug — throw.

3-qadam: Promise qoidalari. Bu qoidalar turlarni biladi, shuning uchun ESLint tsconfig.json ni o'qishi kerak (projectService, TS 6 API orqali — Build darsidagi ikki TypeScript):

js
  {
    files: ["assets/js/**/*.ts"],
    ignores: ["**/*.d.ts"],
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
    rules: {
      "@typescript-eslint/no-floating-promises": "error",
      "@typescript-eslint/no-misused-promises": "error",
      "@typescript-eslint/only-throw-error": "error",
    },
  },

Birinchi npm run lint — 13 xato: 11 ta no-floating-promises (serverdaBajar(…) ×5, sinxronla() ×2, ochirishniSorab, faylniImportQil ×2, import("./olchov.js").then(…)) va 2 ta no-misused-promises (addEventListener("click", () => sinxronla()) kabi — tinglovchi Promise qaytaradi, brauzer uni kutmaydi).

Ulardan bittasi haqiqiy xato edi: import("./olchov.js") rad etilsa (oflayn va keshda yo'q) — ushlanmagan rejection. Tuzatish:

ts
import("./olchov.js")
  .then(({ olchashniBoshla }) => olchashniBoshla())
  .catch((xato: unknown) => {
    console.warn(`Web Vitals yuklanmadi: ${xatoMatni(xato)}`);
  });

(xato: unknown) — «.catch((e) => …) — any» tuzog'iga qarshi. Qolgan 12 tasi xavfsiz edi: serverdaBajar va sinxronla xatoni o'zi ichida ushlaydi va hech qachon rad etilmaydi. Ular void bilan belgilandi — "natijasi ataylab kutilmaydi":

ts
// Promise hech qachon reject bo'lmaydi (xato ichida ushlanadi) —
// chaqiruvchilar kutmaydi, "void" bilan (no-floating-promises)
void serverdaBajar(/* … */);
qaytaTugma.addEventListener("click", () => void sinxronla());

npm run lint endi 2,7 soniya oladi (oldin tezroq edi): turli qoidalar har faylni TypeScript bilan tahlil qiladi.

Turli qoidalar ko'p — biz faqat uchtasini yoqdik. Butun to'plam — tseslint.configs.recommendedTypeChecked. Unda, masalan, restrict-template-expressions (shablon satrga undefined yoki obyekt qo'yilsa — "35000 undefined" kabi chek, Funksiyalarni tiplash darsidagi misol) va unbound-method (metodni klassdan ajratib olish — this yo'qoladi, Function overload va this) bor. Katta loyihada to'plamni to'liq yoqish va keraksiz qoidalarni alohida o'chirish qulayroq.

4-qadam: testlar — paketniTekshir: to'g'ri matn (ok: true, qiymat) va buzuq matn "{" (ok: false, xato — VazifaXatosi, kod: "JSON", throw yo'q).

Natija: npm test — 143/143 (+2). tip, lint, format:check — toza. Brauzer (dist/) — 159/159. Diff: 7 fayl, +110 −41.

bash
git switch -c refactor/natija-turi
# turlar, paket, saqlash, asosiy, eslint.config.js, testlar
npm run build && npm test && npm run tip
npm run lint && npm run format:check
git add .
git commit -m "refactor: Natija (Result) turi, no-floating-promises" \
  -m "paketniTekshir; void bilan kutilmaydigan Promise; olchov import catch"
git push -u origin refactor/natija-turi
gh pr create --fill
gh pr merge --merge

Commit sarlavhasi — 52 belgi.

7. Real ishda

  • Har frontend va backend async kod bilan ishlaydi: Promise<T> turlari, catch (e: unknown) va errorMessage(e) kabi yordamchi deyarli har loyihada bor.
  • no-floating-promises — ko'p jamoalarning majburiy ESLint qoidasi. Node serverda (24-qismdan keyin) ushlanmagan rejection jarayonni to'xtatadi: Node 15 dan beri standart xulq — --unhandled-rejections=throw. Bitta unutilgan await butun serverni o'chirishi mumkin, shuning uchun bu qoida server loyihalarida deyarli majburiy.
  • Result kutubxonalari — neverthrow, Effect: Result zanjirlari, map/andThen. Kichik loyihada o'z 3 qatorlik Result turingiz yetadi.
  • Intervyu: "Nega catch dagi xato unknown?", "Unutilgan await ni qanday topasiz?", "Exception va Result farqi, qaysi biri qachon?".

Xulosa

  • async funksiya — Promise<T> (TS1064). Awaited<T> — Promise ichidagi tur, Promise.all — tuple.
  • Unutilgan await: tsc — TS2339, TS2801; ishlatilmagan Promise'ni esa faqat ESLint'ning no-floating-promises ko'radi.
  • catch (e) — unknown: instanceof Error yoki errorMessage(e: unknown). .catch((e) => …) — any, unga unknown deb o'zingiz yozing.
  • O'z xato klassi — literal union kod va cause bilan.
  • Result<T, E> — kutilgan xato imzoda ko'rinadi va ok tekshiruvisiz qiymatga yetib bo'lmaydi. Bug — throw.

Keyingi dars: JavaScript'dan TypeScript'ga ko'chish — vazifalar ning oxirgi modullari, allowJs o'chadi; katta JS loyihani bosqichma-bosqich ko'chirish usuli.

Manbalar

  • TypeScript 4.4 Release Notes: "Defaulting to the unknown Type in Catch Variables (--useUnknownInCatchVariables)"
  • TypeScript Handbook: "Narrowing", Utility Types: Awaited<Type> — typescriptlang.org
  • typescript-eslint: no-floating-promises, no-misused-promises, only-throw-error, use-unknown-in-catch-callback-variable — typescript-eslint.io/rules
  • MDN: "Error: cause", "Promise.prototype.catch()" — developer.mozilla.org
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
TypeScript'da async kod va xatolar: Promise<T>, catch (e: unknown) va Result<T, E> — IlmHamroh