Phiên bản ES6 giới thiệu cách thức hoàn toàn mới cho việc làm việc với function và iterator là Generator (hay còn gọi là hàm sinh). Generator là một function có khả năng tạm dừng và tiếp tục từ chỗ nó đã dừng. Để giải thích một cách ngắn gọn, generators có chức năng là iterator được bao bọc bên trong function.
Một số thực tế thú vị: async/await được xây dựng dựa trên generators.
Bạn đang xem: Hiểu Generators trong Javascript dễ dàng thông qua ví dụ
Generators có mối quan hệ chặt chẽ với iterators. Nếu bạn chưa biết về iterators, bạn có thể đọc nhanh bài viết này.
Dưới đây là một ví dụ thực tế để giúp bạn hiểu rõ hơn về bản chất của generators:
Hãy tưởng tượng bạn đang xem một bộ phim thú vị và đến cảnh căng thẳng nhưng đột nhiên có một người giao hàng gọi bạn ra nhận hàng. Dù bạn đang rất hào hứng (với bộ phim) nhưng bạn vẫn phải dừng lại để đi nhận hàng. Sau khi nhận hàng, bạn lại tiếp tục xem bộ phim tiếp tục từ chỗ bạn đã dừng chứ không xem lại từ đầu. Hành động tiếp tục xem phim đó giống như hành động của một generator.
Giới thiệu
Hãy cùng tìm hiểu xem generator có thể giúp chúng ta giải quyết những vấn đề thông thường trong lập trình như thế nào. Nhưng trước tiên, hãy định nghĩa generator là gì.
Generators là gì
Một function thông thường như dưới đây không thể tạm dừng trước khi hoàn thành công việc của nó (các dòng lệnh cuối cùng được thực thi)
function normalFunc() {
console.log('I');
console.log('cannot');
console.log('be');
console.log('stopped.');
}
Để thoát khỏi function normalFunc, chúng ta chỉ có thể sử dụng lệnh return hoặc throw lỗi. Nếu chúng ta gọi function này lại, nó sẽ được thực thi từ đầu.
Ngược lại, generator là một function có thể tạm dừng và tiếp tục từ chỗ nó đã dừng.
Dưới đây là một số định nghĩa thông dụng về generator:
- Generator là một lớp đặc biệt của function giúp đơn giản hóa việc implement một iterator.
- Generator là một function sinh ra nhiều kết quả tuần tự thay vì chỉ trả về một giá trị duy nhất.
Trong JavaScript, generator là một function trả về một object mà chúng ta có thể gọi next() trên. Mỗi lần gọi next(), chúng ta nhận được một object có dạng sau:
false
Xem thêm : Gel là gì? Ưu điểm mỹ phẩm dạng gel là gì mà được yêu thích thế?
Thuộc tính value chứa giá trị trả về. Thuộc tính done có thể là true hoặc false. Khi done là true, generator sẽ tạm dừng và không sinh ra giá trị nào nữa.
Dưới đây là hình minh họa:
Từ khóa yield có chức năng tương tự như return, trả về giá trị được định nghĩa sau nó. Tuy nhiên, yield lưu trạng thái của hàm tại vị trí yield được gọi, để lần gọi next() tiếp theo có thể tiếp tục từ đó. Đó là lý do tại sao chúng ta nói rằng hàm “yield” một giá trị thay vì trả về.
Tạo một Generator
Dưới đây là ví dụ để tạo một generator trong Javascript:
function * generatorFunction() {
console.log('This will be executed first.');
yield 'Hello, ';
console.log('I will be printed after the pause');
yield 'World!';
}
const generatorObject = generatorFunction();
console.log(generatorObject.next().value);
console.log(generatorObject.next().value);
console.log(generatorObject.next().value);
Chúng ta sử dụng cú pháp function * thay vì function để khai báo một generator. Vì nó cũng chỉ là một function, chúng ta có thể sử dụng đây ở bất kỳ đâu giống như với các function thông thường.
Ngoài ra, chúng ta có thể return từ generator. Tuy nhiên, khi gọi return, giá trị của thuộc tính done sẽ được thiết lập thành true, điều này có nghĩa là generator sẽ không sinh ra giá trị mới nữa.
Ở dòng số 3, chúng ta tạo ra một object generator. Nếu bạn bối rối vì nó giống việc gọi hàm, hãy yên tâm, bạn đang làm đúng: khi gọi hàm generator, nó trả về một object generator. Object generator này là một iterator, cho phép chúng ta sử dụng for-of loop hoặc trong các function khác nhận đối số là iterable.
Ở dòng 4, chúng ta gọi hàm next() trên generatorObject. Với lời gọi này, generator bắt đầu thực thi chức năng của nó. Trước tiên, nó console.log dòng “This will be executed first.”. Sau đó, sau khi chạy qua câu lệnh “yield ‘Hello’, generator yield ra một object với giá trị sau: { value: ‘Hello, ‘, done: false }, sau đó dừng lại.
Ở dòng 5, chúng ta lại gọi next(). Lần này, generator tiếp tục từ chỗ nó đã dừng. Đầu tiên, nó console.log chuỗi “I will be printed after the pause”. Một lần nữa, nó gặp yield và yield ra một object với giá trị sau: { value: ‘World!’, done: false }. Chúng ta chỉ trích xuất thuộc tính value để in giá trị này. Generator tiếp tục bị dừng lại.
Ở dòng 6, chúng ta lại gọi next(). Lần này, không còn chuỗi để in ra nữa. Trong trường hợp này, generator return một object { value: undefined, done: true } thay vì yield. Giá trị của thuộc tính done được thiết lập thành true.
Xem thêm : NHỮNG ĐIỀU CẦN BIẾT VỀ VAI ÁO VEST NAM
Trong trường hợp chúng ta muốn chạy generator từ đầu, chúng ta phải tạo một generator mới.
Khi nào sử dụng Generator
Có rất nhiều tình huống trong việc sử dụng generator có thể giúp chúng ta. Hãy xem một số tình huống đó:
Implement một iterables
Thường thì khi chúng ta implement một iterator, chúng ta phải tạo một object iterator thủ công với function next(). Ngoài ra, chúng ta cũng phải lưu trạng thái của nó thủ công. Vì generators cũng là iterables, chúng ta có thể dùng chúng để implement một iterables một cách dễ đọc và ngắn gọn hơn. Hãy xem ví dụ dưới đây:
Đặt vấn đề: Implement một iterable trả về “This”, “is”, và “iterable”. Code dưới đây sẽ sử dụng iterator để implement một iterable:
const iterableObj = {
[Symbol.iterator]() {
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 hai phiên bản trên, chúng ta có thể thấy phiên bản sử dụng generator vượt trội hơn vì:
- Không cần phải implement function next().
- Không cần phải tạo object trả về theo cách thủ công, ví dụ như { value: ‘This’, done: false } như ở ví dụ trên.
- Không cần phải quan tâm đến trạng thái của function. Với iterator, chúng ta phải khởi tạo biến step để lưu trạng thái. Biến trạng thái này sẽ quyết định giá trị trả về của iterable. Với generator, chúng ta không cần lo lắng về điều này.
Tạo luồng dữ liệu vô tận
Chúng ta có thể tạo một generator có khả năng sinh ra 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
// 2
Sử dụng Generator như một observer
Chúng ta có thể sử dụng hàm next(val) để gửi dữ liệu đến generator. Mỗi lần gọi hàm next(), chúng ta đang “đánh thức” generator. Do đó, chúng ta có thể coi generator như một observer vì nó luôn quan sát dữ liệu được truyền vào và có hành vi cụ thể tương ứng.
Ưu điểm của Generators
Lazy evaluation
Như trong ví dụ về luồng dữ liệu vô tận, chúng ta có thể làm được điều đó nhờ khái niệm “lazy evaluation”. Lazy evaluation là một mô hình tính toán mà tính toán của một biểu thức được trì hoãn cho đến khi chúng ta cần. Điều này có nghĩa là nếu chúng ta không cần giá trị đó, nó sẽ không được tính toán. Xem ví dụ dưới đây:
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), chúng ta chỉ tạo ra một object generator, không có giá trị nào được tính toán. Sau đó, nếu chúng ta gọi next(), nó sẽ tính toán giá trị 9 và trả về kết quả.
Sử dụng tối ưu bộ nhớ
Một lợi ích của lazy evaluation là generator sử dụng bộ nhớ một cách hiệu quả. Chúng ta chỉ sinh ra những giá trị mà chúng ta thực sự cần. Đối với các function thông thường, chúng ta phải sinh ra tất cả các giá trị và giữ chúng để có thể sử dụng sau này. Tuy nhiên, với generator, chúng ta có thể trì hoãn tính toán này cho đến khi chúng ta thực sự cần giá trị trả về.
Kết
Hy vọng qua bài viết này, bạn đã hiểu rõ hơn về Generators. Hẹn gặp lại trong các bài viết tiếp theo.