ES 6 giới thiệu một cách thức hoàn toàn mới cho phép chúng ta làm việc với function và iterator là Generator (hay hàm sinh). Generator là function có thể thể dừng giữa chừng và sau đó tiếp tục từ chỗ mà nó đã dừng. Nói một cách ngắn gọn thì generators nó là iterator đội lốt function =))

Fun fact: async/await được xây dựng dựa trên generators.

Bạn đang xem: Generator là gì

Generators có mối liên hệ mật thiết với iterators. Nếu các bạn chưa biết về iterator thì các bạn có thể xem nhanh bài viết này nhé

*

yield ở đây có chức năng tương tự như return ở chỗ nó "trả về" giá trị được định nghĩa sau nó. Điểm khác của nó là ở chỗ nó lưu lại trạng thái của hàm tại vị trí yield được gọi nhằm mục đích ở lần gọi next() tiếp theo của chúng ta nó có thể chạy tiếp từ chỗ nó đang chạy dở. Đó là lý do keyword yield ra đời, chúng ta sẽ nói là hàm x yield một giá trị chứ không phải là return

Tạo một Generator

Sau đây là ví dụ để tạo một generator trong Javascript:

function * generatorFunction() { // Line 1 console.log("This will be executed first."); yield "Hello, "; // Line 2 console.log("I will be printed after the pause"); yield "World!";}const generatorObject = generatorFunction(); // Line 3console.log(generatorObject.next().value); // Line 4console.log(generatorObject.next().value); // Line 5console.log(generatorObject.next().value); // Line 6// This will be executed first.// Hello, // I will be printed after the pause// World!// undefinedChúng ta sử dụng cú pháp function * thay vì function để khai báo một generator. Do nó cũng chỉ là function nên chúng ta có thể sử dụng ở bất kỳ đâu như đối với function thông thường.

Ngoài ra thì chúng ta có thể return từ generator. Tuy nhiên thì khi gọi return sẽ thiết lập giá trị cho done thành true - như vậy là generator sẽ không thể sinh ra thêm giá trị nào nữa:

Ở dòng thứ 3, chúng ta đang tạo ra một object generator. Nếu các bạn đang bối rối vì thấy nó giống như đang gọi hàm thì các bạn yên tâm là các bạn đang đúng nhé =)): khi chúng ta gọi hàm generator thì cái mà nó trả về là một object generator. Object generator này là một iterator nên chúng ta có thể sử dụng nó trong vòng lặp for-of hoặc trong các fucntion khác chấp nhận đối số truyền vào là iterable.

Ở dòng 4 thì chúng ta gọi hàm next() trên generatorObject. Với lời gọi này thì generator bắt đầu được thực thi chức năng của nó. Đầu tiên thì nó console.log dòng This will be executed first. Sau đó thì khi nó chạy hết dòng yield "Hello, thì generator sẽ yield ra một object có nội dung như sau: { value: "Hello, ", done: false } và tạm dừng.

Ở dòng thứ 5 thì chúng ta lại gọi next(). Lần này thì generator sẽ lại chạy tiếp bắt đầu từ chỗ nó đang dừng. Đầu tiên nó sẽ console.log xâu I will be printed after the pause. Một lần nữa nó lại gặp yield, objext được yield có nội dung là { value: "World!", done: false }. Chúng ta sẽ chỉ extract thuộc tính value để in nó. Generator lại tiếp tục bị tạm dừng.

Ở dòng thứ 6 thì chúng ta lại gọi next(). Lần này thì không còn string nào để in ra nữa. Trong trường hợp này thì generators sẽ return một object là { value: undefined, done: true} thay vì yield. Giá trị của cờ done được set thành true ,

Trong trường họp chúng ta muốn chạy lại generator từ đầu thì sẽ phải tạo một generator mới.

Xem thêm: Office 2007 Full Crack Sinhvienit, Tải Microsoft Office 2007 Full Rack

Khi nào thì sử dụng Generator

Có rất nhiều tình huống thiết thực mà chúng ta sẽ được hưởng lợi từ việc sử dụng generator. Chúng ta hãy cùng xem nhé.

Implement một iterables

Bình thường khi chúng ta implement một iterator thì chúng ta sẽ phải tự tạo một object iterator thủ công với function next(). Ngoài ra thì state của nó chúng ta cũng phải lưu thủ công. Do generators cũng là iterables nên chúng cũng có thể được dùng để implement một iterables một cách ngắn gọn và dễ đọc hơn. Hãy cùng xem một ví dụ nhé:

Đặt vấn đề: Implement một iterable trả về This, is,iterable. Đoạn code bên dưới sẽ sử dụng iterator để implement một iterable:

const iterableObj = { () { let step = 0; return { next() { step++; if (step === 1) { return { value: "This", done: false}; } else if (step === 2) { return { value: "is", done: false}; } else if (step === 3) { return { value: "iterable.", done: false}; } return { value: "", done: true }; } } },}for (const val of iterableObj) { console.log(val);}// This// is // iterable.Và đây là đoạn code sử dụng generator:

function * iterableObj() { yield "This"; yield "is"; yield "iterable."}for (const val of iterableObj()) { console.log(val);}// This// is // iterable.So sánh giữa 2 phiên bản ở trên thì chúng ta có thể thấy phiên bản sử dụng generator vượt trội hơn hẳn do:

Chúng ta không phải implement function next()Chúng ta không phải soạn nội dung object trả về một cách thủ công, ví dụ như ở trên là { value: "This", done: false }Chúng ta không phải quan tâm đến state của function. Như ở trong ví dụ với iterator chúng ta phải khởi tạo biến step để lưu state. Biến state này sẽ quyết định output từ iterable. Với generator thì chúng ta không phải quan tâm đến vấn đề này.Tạo luồng dữ liệu vô tận

Chúng ta có thể tạo ra một generator với khả năng sinh dữ liệu vô tận, ví dụ:

function * naturalNumbers() { let num = 1; while (true) { yield num; num = num + 1 }}const numbers = naturalNumbers();console.log(numbers.next().value)console.log(numbers.next().value)// 1// 2Có thể sử dụng Generator như một observerChúng ta có thể dùng hàm next(val) để gửi kèm dữ liệu đến cho generator. Mà mỗi lần chúng ta gọi gửi value đến cho generator như vậy là chúng ta đang "đánh thức" generator dậy. Chính vì vậy nên chúng ta có thể coi generator như là một observer do nó luôn quan sát value được truyền vào và sẽ có hành vi cụ thể kèm theo tương ứng.

Ưu điểm của Generators

Lazy evaluation

Như trong ví dụ luồng dữ liệu vô tận thì chúng ta có thể làm được như vậy là nhờ lazy evaluation. Lazy evaluation là một mô hình tính toán sẽ trì hoãn việc tính toán của một biểu thức cho đến khi nào chúng ta cần. Như vậy nghĩa là nếu chúng ta không cần đến nó thì nó sẽ không được tính toán. Hãy xem ví dụ sau nhé:

function * powerSeries(number, power) { let base = number; while(true) { yield Math.pow(base, power); base++; }}Khi chúng ta gọi powersOf2 = powerSeries(3, 2); thì chúng ta mới chỉ đơn thuần là tạo ra một object generator, chưa có giá trị nào được tính toán. Sau đó, nếu chúng ta gọi tiếp next() thì nó sẽ tính toán ra giá trị 9 và trả về kết quả.

Sử dụng tối ưu bộ nhớ

Một hệ quả của Lazy Evaluation là bộ nhớ sẽ được generator sử dụng một cách tối ưu. Do chúng ta sẽ chỉ sinh ra các values mà chúng ta cần. Với các function thông thường thì chúng ta sẽ phải sinh ra trước các values và giữ chúng để có thể dùng về sau. Tuy nhiên thì với generator thì chúng ta có thể trì hoãn sự tính toán này lại cho đến khi chúng ta thực sự cần value trả về của nó.

Kết

Hi vọng qua bài viết này các bạn đã có cái nhìn rõ ràng hơn về Generators. Hẹn gặp lại các bạn trong các bài viết sau.

Happy coding ~

Bài viết liên quan

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *