Kodokon kodokon.com

Iterator, generator และโปรโตคอลการวนซ้ำ

เชี่ยวชาญโปรโตคอลที่อยู่เบื้องหลัง for...of, spread และ destructuring แล้วใช้ generator เพื่อสร้างลำดับแบบ lazy

9 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

มีโปรโตคอลสองอย่างที่แตกต่างกันควบคุมการวนซ้ำ อ็อบเจกต์จะเป็น iterable ถ้ามันเปิดเผยเมท็อดภายใต้คีย์ Symbol.iterator เมท็อดนั้นต้องคืน iterator นั่นคือ อ็อบเจกต์ที่มีเมท็อด next() ซึ่งคืน { value, done } ทุกอย่างที่บริโภคลำดับ - for...of, spread operator ..., destructuring, Array.from, Promise.all - ล้วนผ่านโปรโตคอลเหล่านี้ ดังนั้นคุณจึงทำให้อ็อบเจกต์ใด ๆ เข้ากันได้

JAVASCRIPT
const range = {
  from: 1,
  to: 3,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        return current <= last
          ? { value: current++, done: false }
          : { value: undefined, done: true };
      }
    };
  }
};

console.log([...range]); // [1, 2, 3]
iterable ที่เขียนขึ้นด้วยมือ โดยไม่มีคลาสหรือ generator

การเขียน iterator ด้วยมือนั้นยืดยาว Generator (function*) สร้าง iterator ให้โดยอัตโนมัติ แต่ละ yield จะหยุดพักการทำงานและรักษาสถานะโลคัลของฟังก์ชันเอาไว้ การประเมินค่าแบบ lazy นี้ช่วยให้คุณจำลองลำดับที่ไม่มีที่สิ้นสุดได้ ตราบเท่าที่ผู้บริโภคจำกัดจำนวนค่าที่ร้องขอ

JAVASCRIPT
function* naturals() {
  let n = 0;
  while (true) yield n++;
}

function* take(iterable, count) {
  let index = 0;
  for (const value of iterable) {
    if (index >= count) return;
    yield value;
    index += 1;
  }
}

console.log([...take(naturals(), 4)]); // [0, 1, 2, 3]
ลำดับที่ไม่มีที่สิ้นสุดถูกบริโภคโดยไม่บล็อกเลย

iterator ของ generator ยังเปิดเผย return() และ throw() ด้วย รายละเอียดในสเปกที่มักถูกมองข้าม เมื่อคุณออกจากลูป for...of ด้วย break, return หรือ exception เอนจินจะเรียก iterator.return() ให้ โดยอัตโนมัติ ใน generator สิ่งนี้จะรันบล็อก finally ที่ค้างอยู่ ซึ่งเป็นจุดที่เหมาะสำหรับปล่อยทรัพยากร (ไฟล์ การเชื่อมต่อ ล็อก)

JAVASCRIPT
function* readLines() {
  try {
    yield "line 1";
    yield "line 2";
  } finally {
    console.log("cleanup");
  }
}

for (const line of readLines()) {
  console.log(line);
  break; // triggers return(), hence the finally
}
// line 1, cleanup
break เรียก return(): บล็อก finally จึงรัน

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. อะไรทำให้อ็อบเจกต์เป็น iterable ในความหมายของโปรโตคอล?
    • การมีพร็อพเพอร์ตี้ length และดัชนีที่เป็นตัวเลข
    • การเปิดเผยเมท็อด Symbol.iterator ที่คืนอ็อบเจกต์ซึ่งมี next()
    • การสืบทอดจาก Array.prototype
    • การมีเมท็อด next() โดยตรง
  2. เกิดอะไรขึ้นเมื่อคุณออกจากลูป for...of ด้วย break ขณะที่มันกำลังบริโภค generator?
    • ไม่มีอะไรพิเศษ generator ยังคงหยุดพักอยู่ที่ yield ตัวสุดท้าย
    • เอนจินเรียก throw() บน iterator
    • เอนจินเรียก return() ซึ่งรันบล็อก finally ของ generator
    • generator เริ่มต้นใหม่จากจุดเริ่มต้นในการวนซ้ำครั้งถัดไป
  3. ทำไมคุณจึงทำงานกับ generator ที่ไม่มีที่สิ้นสุดได้โดยไม่บล็อกโปรแกรม?
    • เอนจินจำกัดจำนวนรอบการวนซ้ำโดยอัตโนมัติ
    • generator รันบนเธรดที่แยกต่างหาก
    • การประเมินค่าเป็นแบบ lazy: แต่ละค่าจะถูกคำนวณเมื่อมีการเรียก next() เท่านั้น
    • ค่าต่าง ๆ ถูกคำนวณล่วงหน้าและแคชไว้โดยเอนจิน