Iterator va Generator

~25 daq · JavaScript · Ilg'or mavzular

Muammodan boshlaylik

Ikkita vaziyat:

Birinchi: Siz 1 dan 1 000 000 gacha sonlar ketma-ketligini yaratmoqchisiz. Massiv bilan: [1, 2, 3, ..., 1000000] — bu 1 million elementni xotirada saqlaydi. Faqat bittasini ishlatsangiz ham — hammasi yuklanadi.

Ikkinchi: Fibonacci sonlar ketma-ketligi — cheksiz. [1, 1, 2, 3, 5, 8, 13, ...] — oxiri yo'q. Buni massivga sig'dirib bo'lmaydi.

Massiv bu hollarda to'g'ri tanlov emas. Kerak bo'lganida, bitta-bitta bering — oldindan hammani tayyorlamang.

Bu muammoni Iterator va Generator hal qiladi.


Iterator nima?

Iterator — "keyingi elementni ber" deb so'rash imkoniyati. Navbatma-navbat, bittadan.

Hayotiy o'xshatma

Poliklinika navbati. Palatada 50 nafar bemor bor. Ularni birdaniga kabinet ichiga kiritish mumkin emas. Hamshira "keyingisi!" deydi — biri kiradi, chiqadi, keyin yana "keyingisi!" Navbat tugaguncha davom etadi.

Iterator — o'sha "keyingisi!" tizimi. Siz next() deysiz — u bitta element beradi. Yana next() — yana bitta. Tugaganda — "tamom" belgisi beradi.


Iterator protokoli

Iterator — next() metodi bo'lgan ob'ekt. next() chaqirilganda { value, done } ob'ektini qaytaradi:

  • value — hozirgi element
  • done — false bo'lsa davom etmoqda, true bo'lsa tugadi
// Oddiy iterator yaratish — qo'lda
function sonlarIteratori(boshlanish, tugash) {
  let hozirgi = boshlanish;

  return {
    next() {
      if (hozirgi <= tugash) {
        return { value: hozirgi++, done: false };
      } else {
        return { value: undefined, done: true };
      }
    }
  };
}

const iterator = sonlarIteratori(1, 4);

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

Har next() — bitta qadam. 1 million sonlar uchun ham xotiraga barchasi yuklanmaydi — faqat hozirgi son.


Iterable — for...of bilan ishlash

Siz for...of ni massivda ishlatgansiz. Lekin for...of aslida qanday ishlaydi?

for...of — ob'ektdan [Symbol.iterator]() metodini chaqiradi, iterator oladi, keyin done: true bo'lguncha next() chaqiraveradi.

const massiv = [10, 20, 30];

// for...of ichida nima bo'layotgani:
const iterator = massiv[Symbol.iterator]();
console.log(iterator.next()); // { value: 10, done: false }
console.log(iterator.next()); // { value: 20, done: false }
console.log(iterator.next()); // { value: 30, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

Demak massiv — iterable, chunki unda [Symbol.iterator] metodi bor. String, Map, Set ham iterable.

O'z iterable ob'ektini yaratish

const diapazon = {
  bosh: 1,
  oxir: 5,

  [Symbol.iterator]() {        // ← for...of shu metodga murojaat qiladi
    let hozirgi = this.bosh;
    const oxir  = this.oxir;

    return {
      next() {
        if (hozirgi <= oxir) {
          return { value: hozirgi++, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

for (const son of diapazon) {
  console.log(son); // 1, 2, 3, 4, 5
}

// Spread ham ishlaydi
console.log([...diapazon]); // [1, 2, 3, 4, 5]

Generator — iterator yozishni osonlashtiradi

Yuqoridagi iterator kodi ko'p — next(), { value, done }, holat saqlash. Generator — bularni avtomatik qiladi.

Generator — function* bilan e'lon qilinadigan maxsus funksiya. yield kalit so'zi — "mana bu qiymat, to'xta — keyingi next() da davom et".

Hayotiy o'xshatma

Kitob o'qish. Har kuni bir bob o'qiysiz — kitobni yopasiz, joy belgilanadi. Ertasi kuni davom etasiz o'sha joydan. Qaytadan boshlamaysiz.

Generator funksiya — o'sha kitob. yield — joy belgisi. Funksiya qolipida "muzlaydi", next() da davom etadi. Holat saqlanib qoladi.

function* sonlarGeneratori(bosh, oxir) {
  for (let i = bosh; i <= oxir; i++) {
    yield i;           // ← "mana i, to'xta"
  }
  // ← funksiya tugadi — done: true
}

const gen = sonlarGeneratori(1, 4);

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: 4, done: false }
console.log(gen.next()); // { value: undefined, done: true }

for...of bilan:

for (const son of sonlarGeneratori(1, 5)) {
  console.log(son); // 1, 2, 3, 4, 5
}

Generator avtomatik iterable — [Symbol.iterator] o'zi bor.


Cheksiz ketma-ketlik

Generator cheksiz bo'lishi mumkin — yield doim bo'lsa, funksiya hech qachon tugamaydi. Lekin siz qachon to'xtashni belgilaysiz.

function* fibonacci() {
  let [oldingi, hozirgi] = [0, 1];

  while (true) {              // ← cheksiz loop — lekin generator muzlaydi
    yield hozirgi;            // ← hozirgi sonni ber, to'xta
    [oldingi, hozirgi] = [hozirgi, oldingi + hozirgi];
  }
}

const fib = fibonacci();

console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
console.log(fib.next().value); // 5
console.log(fib.next().value); // 8
// Xohlagancha davom etish mumkin — xotira sarflanmaydi

Fibonacci sonlarining barchasi xotirada yo'q — faqat ikkita o'zgaruvchi: oldingi va hozirgi. Har next() da faqat shu ikki qiymat yangilanadi.


Generator ga qiymat berish — yield ikki tomonlama

yield — faqat qiymat chiqarmaydi. next(qiymat) orqali generatarga ham qiymat yuborish mumkin:

function* muloqot() {
  const ism  = yield "Ismingiz nima?";       // ← savol berdi, javob kutdi
  const yosh = yield `Salom, ${ism}! Yoshingiz?`; // ← ism keldi, yana savol
  yield `${ism}, ${yosh} yosh — ajoyib!`;
}

const gen = muloqot();

console.log(gen.next().value);         // "Ismingiz nima?"
console.log(gen.next("Jasur").value);  // "Salom, Jasur! Yoshingiz?"
console.log(gen.next(25).value);       // "Jasur, 25 yosh — ajoyib!"

Birinchi next() ga argument berish shart emas — birinchi yield gacha yetib boradi.


Amaliy misol — sahifalangan ma'lumot yuklash

Generator bilan sahifama-sahifa ma'lumot yuklash qulay:

async function* sahifalab(url) {
  let sahifa = 1;

  while (true) {
    const javob  = await fetch(`${url}?sahifa=${sahifa}`);
    const data   = await javob.json();

    if (data.items.length === 0) break; // ← ma'lumot tugadi

    yield data.items;   // ← shu sahifaning elementlarini ber
    sahifa++;
  }
}

// Ishlatish
for await (const elementlar of sahifalab("/api/mahsulotlar")) {
  elementlar.forEach(el => ko_rsat(el));

  // Biror shartda to'xtatish mumkin
  if (sahifaYetarli()) break;
}

Bu yerda for await...of — async generator uchun. Har iteratsiyada yangi sahifa yuklanadi — oldindan hammasi emas.


Tez-tez uchraydigan xatolar

Generator funksiya chaqirilsa — darhol ishlamaydi:

function* hisob() {
  console.log("Boshladi");
  yield 1;
  console.log("Davom etdi");
  yield 2;
}

const gen = hisob(); // ← "Boshladi" chiqmadi! Generator hozirgina yaratildi

gen.next(); // "Boshladi" — endi birinchi yield gacha ishga tushdi
gen.next(); // "Davom etdi" — ikkinchi yield gacha

Generator funksiyasi chaqirilganda — ichidagi kod ishlamaydi. Faqat iterator ob'ekt yaratiladi. Kod next() da boshlaydi.

yield dan keyin return — done: true:

function* misol() {
  yield 1;
  yield 2;
  return "tugadi"; // ← { value: "tugadi", done: true }
  yield 3;         // ← hech qachon yetib kelmaydi
}

const gen = misol();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: "tugadi", done: true }
console.log(gen.next()); // { value: undefined, done: true }

for...of done: true bo'lgan value ni e'tiborsiz qoldiradi — faqat done: false lar ko'rsatiladi.

Bir xil generatorni ikki joyda ishlatish:

Generator ob'ekti — bir yo'nalishli. Tugagan generator qaytadan boshlanmaydi.

function* gen() { yield 1; yield 2; }

const iterator = gen();

for (const v of iterator) { console.log(v); } // 1, 2
for (const v of iterator) { console.log(v); } // ← hech narsa — tugab bo'ldi

// ✅ — har safar yangi generator
for (const v of gen()) { console.log(v); } // 1, 2
for (const v of gen()) { console.log(v); } // 1, 2

Xulosa

Iterator — next() metodi orqali elementlarni bittadan beruvchi ob'ekt. { value, done } protokoli. Xotira tejash — hammasi oldindan yuklanmaydi.

Iterable — [Symbol.iterator]() metodi bo'lgan ob'ekt. for...of, spread, destructuring shu metoddan foydalanadi.

Generator — function* va yield bilan yoziladigan iterator. Holat avtomatik saqlanadi, yield da "muzlaydi", next() da davom etadi.

Qachon kerak:

  • Katta yoki cheksiz ketma-ketliklar
  • Xotirani tejash — hammasi oldindan yuklanmasin
  • for...of bilan ishlaydigan o'z ob'ektlari
  • Sahifalangan ma'lumot yuklash

Keyingi dars

59-dars: Symbol — nima va qachon kerak