Tìm Hiểu Về Generator Function Và Cơ Chế Yield Trong JavaScript

Giới thiệu về Generator

Trong ECMAScript 6, một cú pháp đặc biệt cho phép tạm dừng và tiếp tục thực thi hàm đã được đưa vào, gọi là Generator. Về mặt hình thức, một generator trông giống như một hàm bình thường, nhưng điểm khác biệt lớn nhất là khả năng trả về giá trị nhiều lần. Thông qua từ khóa yield, luồng thực thi của hàm có thể bị tạm ngưng (pause), tạo ra khả năng điều khiển luồng chạy linh hoạt, đặc biệt hữu ích trong việc xử lý các tác vụ bất đồng bộ.

Các phương thức điều khiển

Đối tượng Generator cung cấp ba phương thức chính để tương tác với trạng thái bên trong của nó:

  • Generator.prototype.next(): Tiếp tục thực thi hàm cho đến điểm yield tiếp theo và trả về giá trị tại đó.
  • Generator.prototype.return(): Ép buộc kết thúc generator và trả về một giá trị chỉ định.
  • Generator.prototype.throw(): Ném một lỗi vào bên trong generator tại vị trí đang tạm dừng.

Cơ chế hoạt động và ví dụ cơ bản

Khi sử dụng cú pháp function* để khai báo, bạn đang định nghĩa một generator function. Hàm này không thực thi ngay lập tức mà trả về một đối tượng iterator. Có thể hình dung generator như một máy trạng thái (state machine) encapsulate nhiều trạng thái nội bộ. Con trỏ thực thi sẽ di chuyển từ đầu hàm hoặc vị trí dừng trước đó đến điểm yield kế tiếp mỗi khi phương thức next() được gọi.

Phương thức next() trả về một object gồm hai thuộc tính: value (giá trị của biểu thức yield) và done (boolean xác định generator đã hoàn thành chưa). Nếu sử dụng yield*, quyền điều khiển sẽ được ủy thác cho một generator khác.

function* createSeries(base) {
    yield base * 2;
    yield base * 3;
    return base * 4;
}

const iterator = createSeries(5);
console.log(iterator); // Object generator đang tạm dừng
console.log(iterator.next()); // {value: 10, done: false}
console.log(iterator.next()); // {value: 15, done: false}
console.log(iterator.next()); // {value: 20, done: true}
console.log(iterator.next()); // {value: undefined, done: true}

Truyền tham số vào Generator

Khi gọi next(), nếu có truyền đối số, giá trị này sẽ trở thành kết quả của biểu thức yield gần nhất vừa được thực thi trước đó. Điều này cho phép giao tiếp hai chiều giữa code gọi và generator.

function* calculateSum(start) {
    let inputVal = yield start * 2;
    console.log('Nhận giá trị:', inputVal);
    yield start + inputVal;
    console.log('Hoàn tất:', start, inputVal);
    return start * 10;
}

const proc = calculateSum(5);
console.log(proc.next()); // {value: 10, done: false}
console.log(proc.next(100)); // {value: 105, done: false} - inputVal nhận 100
console.log(proc.next()); // {value: 50, done: true}

Ủy thác thực thi với yield*

Biểu thức yield* cho phép một generator gọi và ủy quyền thực thi cho một generator khác. Luồng chạy sẽ đi vào generator được ủy thác cho đến khi nó hoàn thành.

function* subGenerator() {
    yield 10;
    yield 20;
    return 30;
}

function* mainGenerator(val) {
    yield val * 2;
    yield* subGenerator();
    yield val * 5;
}

const main = mainGenerator(2);
console.log(main.next()); // {value: 4, done: false}
console.log(main.next()); // {value: 10, done: false}
console.log(main.next()); // {value: 20, done: false}
console.log(main.next()); // {value: 10, done: false}
console.log(main.next()); // {value: undefined, done: true}

Ứng dụng: Đồng bộ hóa thao tác bất đồng bộ

Một trong những ứng dụng mạnh mẽ nhất của generator là biến các thao tác bất đồng bộ trông giống như đồng bộ (linear flow), giúp tránh "callback hell".

let activeIterator = null;

function delayedTask() {
    const randomVal = Math.random() * 5;
    setTimeout(function() {
        if (activeIterator) activeIterator.next(randomVal);
    }, 1000);
}

function handleResults(res1, res2, res3) {
    console.log(res1, res2, res3);
}

function* processChain() {
    const result1 = yield delayedTask();
    console.log('Task 1 xong:', result1);
    const result2 = yield delayedTask();
    console.log('Task 2 xong:', result2);
    const result3 = yield delayedTask();
    console.log('Task 3 xong:', result3);
    handleResults(result1, result2, result3);
}

activeIterator = processChain();
activeIterator.next();

Thẻ: JavaScript es6 generator-function iterator-protocol async-control

Đăng vào ngày 28 tháng 8 lúc 18:18