tek4

Generator trong JavaScript

by - September. 26, 2021
Kiến thức
Học
<p><img class="aligncenter wp-image-8258 size-full" src="https://tek4.vn/wp-content/uploads/2021/02/Capture-70.png" alt="Generator trong JavaScript" width="748" height="440" /></p> <p>Trong b&agrave;i viết n&agrave;y, ta sẽ c&ugrave;ng t&igrave;m hiểu về Generator trong JavaScript c&ugrave;ng với sự trợ gi&uacute;p của c&aacute;c v&iacute; dụ dẫn chứng.</p> <h1>Generator trong JavaScript</h1> <p>Trong JavaScript, Generator cung cấp một c&aacute;ch kh&aacute;c để l&agrave;m việc với c&aacute;c h&agrave;m v&agrave; c&aacute;c đối tượng Iterator.</p> <p>Bằng c&aacute;ch sử dụng Generators,</p> <ul> <li>Ta c&oacute; thể dừng việc thực thi một h&agrave;m từ bất kỳ đ&acirc;u b&ecirc;n trong h&agrave;m.</li> <li>V&agrave; tiếp tục thực thi đoạn m&atilde; từ vị tr&iacute; bị tạm dừng.</li> </ul> <h2>Tạo Generator trong JavaScript</h2> <p>Để tạo một Generator, trước ti&ecirc;n ta cần định nghĩa một h&agrave;m Generator bằng <code class="EnlighterJSRAW" data-enlighter-language="generic">function*</code>. C&aacute;c đối tượng của c&aacute;c h&agrave;m Generator được gọi l&agrave; Generator.</p> <p>V&iacute; dụ:</p> <pre class="EnlighterJSRAW" data-enlighter-language="js">function* vi_du() { &nbsp;&nbsp; //Đoạn m&atilde; } const obj = vi_du();</pre> <p><strong><span style="background-color: #ffff00;">Ch&uacute; &yacute;: H&agrave;m Generator được k&yacute; hiệu l&agrave; *. Ta c&oacute; thể sử dụng function *func(){...} hoặc function* func(){...}.</span></strong></p> <h2>Sử dụng từ kh&oacute;a yield để tạm dừng việc thực thi</h2> <p>Như đ&atilde; đề cập ở tr&ecirc;n, ta c&oacute; thể tạm dừng thực thi một h&agrave;m của h&agrave;m Generator m&agrave; kh&ocirc;ng cần thực thi to&agrave;n bộ th&acirc;n h&agrave;m. Để thực hiện điều đ&oacute;, ch&uacute;ng ta sẽ sử dụng từ kh&oacute;a <code class="EnlighterJSRAW" data-enlighter-language="generic">yield</code>.</p> <p>V&iacute; dụ:</p> <pre class="EnlighterJSRAW" data-enlighter-language="js">function* vi_du() { &nbsp;&nbsp;&nbsp; console.log("Thực thi lần 1"); &nbsp;&nbsp;&nbsp; yield 3; &nbsp;&nbsp; console.log("Thực thi lần 2"); &nbsp;&nbsp;&nbsp; yield 7; } const obj = vi_du(); console.log(obj.next());</pre> <p>Kết quả:</p> <pre>Thực thi lần 1 {value: 3, done: false}</pre> <p>Ở đ&acirc;y,</p> <ul> <li>Một đối tượng Generator c&oacute; t&ecirc;n l&agrave; obj.</li> <li>Khi obj.next() được gọi, đoạn m&atilde; tại c&uacute; ph&aacute;p yield đầu ti&ecirc;n được thực thi. Khi gặp phải từ kh&oacute;a yield, chương tr&igrave;nh trả về gi&aacute; trị v&agrave; tạm dừng h&agrave;m Generator của chương tr&igrave;nh.</li> </ul> <p><strong><span style="background-color: #ffff00;">Ch&uacute; &yacute;: Ta cần g&aacute;n c&aacute;c đối tượng Generator cho một biến trước khi sử dụng.</span></strong></p> <h2>C&aacute;ch hoạt động của c&aacute;c c&acirc;u lệnh yield</h2> <p>Biểu thức sử dụng từ kh&oacute;a <code class="EnlighterJSRAW" data-enlighter-language="generic">yield</code>sẽ trả về một gi&aacute; trị. Tuy nhi&ecirc;n, kh&ocirc;ng giống như c&acirc;u lệnh return, n&oacute; kh&ocirc;ng kết th&uacute;c chương tr&igrave;nh. Đ&oacute; l&agrave; l&yacute; do tại sao ta c&oacute; thể tiếp tục thực thi đoạn m&atilde; từ vị tr&iacute; được tạm dừng tại lần cuối c&ugrave;ng.</p> <p>V&iacute; dụ:</p> <pre class="EnlighterJSRAW" data-enlighter-language="js">function* vi_du() { &nbsp;&nbsp;&nbsp; console.log("Thực thi lần 1"); &nbsp;&nbsp;&nbsp; yield 3; &nbsp;&nbsp; console.log("Thực thi lần 2"); &nbsp;&nbsp;&nbsp; yield 7; } const obj = vi_du(); console.log(obj.next()); console.log(obj.next()); console.log(obj.next());</pre> <p>Kết quả:</p> <pre>Thực thi lần 1 {value: 3, done: false} Thực thi lần 2 {value: 7, done: false} {value: undefined, done: true}</pre> <p>Sau đ&acirc;y l&agrave; c&aacute;ch chương tr&igrave;nh hoạt động:</p> <ul> <li>C&acirc;u lệnh obj.next() đầu ti&ecirc;n thực thi đoạn m&atilde; cho đến c&acirc;u lệnh yield đầu ti&ecirc;n v&agrave; tạm dừng việc thực thi chương tr&igrave;nh.</li> <li>Phương thức obj.next() bắt đầu chương tr&igrave;nh từ vị tr&iacute; tạm dừng.</li> <li>Khi tất cả c&aacute;c phần tử được truy cập, n&oacute; sẽ trả về {value: undefined, done: true}.</li> </ul> <h2>Truyền c&aacute;c đối số cho h&agrave;m Generator</h2> <p>V&iacute; dụ:</p> <pre class="EnlighterJSRAW" data-enlighter-language="js">function* vi_du() { &nbsp;&nbsp;&nbsp; let a = yield 'JavaScript'; &nbsp;&nbsp;&nbsp; console.log(a); &nbsp;&nbsp;&nbsp; console.log('Java'); &nbsp;&nbsp;&nbsp; yield 8; } const gen = vi_du(); console.log(gen.next()); console.log(gen.next(10)); console.log(gen.next());</pre> <p>Kết quả:</p> <pre>{value: 'JavaScript', done: false} 10 Java {value: 8, done: false} {value: undefined, done: true}</pre> <p>Trong chương tr&igrave;nh tr&ecirc;n,</p> <ul> <li>Phương thức gen.next() đầu ti&ecirc;n trả về gi&aacute; trị của yield (trong trường hợp n&agrave;y l&agrave; &lsquo;JavaScript&rsquo;). Tuy nhi&ecirc;n, gi&aacute; trị kh&ocirc;ng được g&aacute;n cho biến a trong let a = yield 'JavaScript';</li> </ul> <pre>{value: 'JavaScript', done: false}</pre> <ul> <li>Khi gặp phải gen.next(10), đoạn m&atilde; lại bắt đầu tại let a = yield 'JavaScript'; v&agrave; đối số l&agrave; gi&aacute; trị 10 được g&aacute;n cho biến a. Ngo&agrave;i ra, đoạn m&atilde; c&ograve;n lại sẽ được thực thi cho đến biểu thức chứa từ kh&oacute;a yield thứ hai.</li> </ul> <pre>10 Java {value: 8, done: false}</pre> <p>Khi lệnh gen.next() thứ ba được thực thi, chương tr&igrave;nh trả về {value: undefined, done: true}. Đ&oacute; l&agrave; bởi v&igrave; kh&ocirc;ng c&oacute; c&acirc;u lệnh yield n&agrave;o kh&aacute;c.</p> <h2>Generator được sử dụng để triển khai c&aacute;c đối tượng Iterable</h2> <p>Generator cung cấp một c&aacute;ch dễ d&agrave;ng hơn để triển khai đối tượng Iterable. Nếu ta muốn triển khai đối tượng Iterator theo c&aacute;ch thủ c&ocirc;ng, ta phải tạo một đối tượng Iterator với phương thức next() v&agrave; lưu lại trạng th&aacute;i.</p> <p>V&iacute; dụ:</p> <pre class="EnlighterJSRAW" data-enlighter-language="js">const obj = { &nbsp;&nbsp;&nbsp; [Symbol.iterator]() { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; let a = 0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; next() { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a++; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (a === 1) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return { value: 100, done: false}; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;else if (a === 2) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return { value: 150, done: false}; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if (a === 3) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return { value: 170, done: false}; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return { value: 0, done: true }; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp; } } for (const i of obj) { &nbsp;console.log(i); }</pre> <p>Kết quả:</p> <pre>100 150 170</pre> <p>V&igrave; Generator l&agrave; c&aacute;c đối tượng Iterable, ta c&oacute; thể triển khai đối tượng Iterator theo c&aacute;ch dễ d&agrave;ng hơn. Sau đ&oacute;, ta c&oacute; thể lặp th&ocirc;ng qua c&aacute;c đối tượng Generator bằng v&ograve;ng lặp <code class="EnlighterJSRAW" data-enlighter-language="generic">for...of</code>.</p> <p>V&iacute; dụ:</p> <pre class="EnlighterJSRAW" data-enlighter-language="js">function* vi_du() { &nbsp;&nbsp;&nbsp; yield 150; &nbsp;&nbsp;&nbsp; yield 170; &nbsp;&nbsp;&nbsp; yield 190; } const obj = vi_du(); for (let i of obj) { &nbsp;&nbsp;&nbsp; console.log(i); }</pre> <p>Kết quả:</p> <pre>150 170 190</pre> <h2>C&aacute;c phương thức của Generator</h2> <table> <tbody> <tr> <td style="text-align: center;">Phương thức</td> <td style="text-align: center;">M&ocirc; tả</td> </tr> <tr> <td style="text-align: center;">next()</td> <td>Trả về gi&aacute; trị của yield.</td> </tr> <tr> <td style="text-align: center;">return()</td> <td>Trả về gi&aacute; trị v&agrave; hủy đối tượng Generator.</td> </tr> <tr> <td style="text-align: center;">throw()</td> <td>Đưa ra lỗi v&agrave; hủy đối tượng Generator.</td> </tr> </tbody> </table> <h2>So s&aacute;nh c&acirc;u lệnh return với yield</h2> <table> <tbody> <tr> <td style="text-align: center;" width="312">C&acirc;u lệnh Return</td> <td style="text-align: center;" width="312">C&acirc;u lệnh Yield</td> </tr> <tr> <td width="312">Trả về gi&aacute; trị v&agrave; kết th&uacute;c h&agrave;m.</td> <td width="312">Trả về gi&aacute; trị v&agrave; dừng việc thực thi của h&agrave;m nhưng kh&ocirc;ng kết th&uacute;c h&agrave;m.</td> </tr> <tr> <td width="312">C&oacute; thể sử dụng cho c&aacute;c h&agrave;m th&ocirc;ng thường v&agrave; c&aacute;c h&agrave;m Generator.</td> <td width="312">Chỉ c&oacute; thể sử dụng cho c&aacute;c h&agrave;m Generator.</td> </tr> </tbody> </table> <h2>H&agrave;m Generator c&oacute; sử dụng return</h2> <p>Ta c&oacute; thể sử dụng c&acirc;u lệnh <code class="EnlighterJSRAW" data-enlighter-language="generic">return</code>trong một h&agrave;m Generator. C&acirc;u lệnh <code class="EnlighterJSRAW" data-enlighter-language="generic">return</code>sẽ trả về một gi&aacute; trị v&agrave; kết th&uacute;c h&agrave;m (tương tự như c&aacute;c h&agrave;m th&ocirc;ng thường).</p> <p>V&iacute; dụ:</p> <pre class="EnlighterJSRAW" data-enlighter-language="js">function* vi_du() { &nbsp;&nbsp;&nbsp; yield 100; &nbsp;&nbsp;&nbsp; return 123; &nbsp;&nbsp;&nbsp; console.log("Đoạn m&atilde;"); &nbsp;&nbsp;&nbsp; yield 200; } const gen = vi_du(); console.log(gen.next()); console.log(gen.next()); console.log(gen.next());</pre> <p>Kết quả:</p> <pre>{value: 100, done: false} {value: 123, done: true} {value: undefined, done: true}</pre> <p>Trong chương tr&igrave;nh tr&ecirc;n, khi gặp c&acirc;u lệnh return, n&oacute; sẽ trả về gi&aacute; trị v&agrave; thuộc t&iacute;nh done được đặt th&agrave;nh true, v&agrave; h&agrave;m kết th&uacute;c. Do đ&oacute;, phương thức next() sau c&acirc;u lệnh return kh&ocirc;ng trả về bất cứ gi&aacute; trị g&igrave;.</p> <p><strong><span style="background-color: #ffff00;">Ch&uacute; &yacute;: Ta cũng c&oacute; thể sử dụng phương thức return() thay v&igrave; c&acirc;u lệnh return như generator.return(123); trong đoạn m&atilde; tr&ecirc;n.</span></strong></p> <h2>Phương thức throw được sử dụng với h&agrave;m Generator</h2> <p>Ta c&oacute; thể xử l&yacute; lỗi tr&ecirc;n h&agrave;m Generator bằng phương thức <code class="EnlighterJSRAW" data-enlighter-language="generic">throw()</code>. Việc sử dụng phương thức <code class="EnlighterJSRAW" data-enlighter-language="generic">throw()</code> sẽ tạo ra một lỗi v&agrave; kết th&uacute;c h&agrave;m.</p> <p>V&iacute; dụ:</p> <pre class="EnlighterJSRAW" data-enlighter-language="js">function* vi_du() { &nbsp;&nbsp;&nbsp; yield 150; &nbsp;&nbsp;&nbsp; yield 170; } const gen = vi_du(); console.log(gen.next()); console.log(gen.throw(new Error('Lỗi xảy ra.'))); console.log(gen.next());</pre> <h2>C&ocirc;ng dụng của Generator</h2> <ul> <li>Generator cho ph&eacute;p viết đoạn m&atilde; tr&ocirc;ng dễ nh&igrave;n hơn trong khi viết c&aacute;c t&aacute;c vụ kh&ocirc;ng đồng bộ.</li> <li>Generator cung cấp một c&aacute;ch dễ d&agrave;ng hơn để triển khai đối tượng Iterator.</li> <li>Generator chỉ thực thi đoạn m&atilde; của n&oacute; khi được y&ecirc;u cầu.</li> <li>Generator hoạt động hiệu quả với bộ nhớ.</li> </ul> <p><strong><span style="background-color: #ffff00;">Ch&uacute; &yacute;: Generator đ&atilde; được giới thiệu trong ES6. Một số tr&igrave;nh duyệt c&oacute; thể kh&ocirc;ng hỗ trợ việc sử dụng Generator n&agrave;y.</span></strong></p> <p>Tr&ecirc;n đ&acirc;y l&agrave; kh&aacute;i niệm v&agrave; v&iacute; dụ cơ bản về Generator trong JavaScript. Hy vọng mọi người c&oacute; thể &aacute;p dụng v&agrave;o trong chương tr&igrave;nh của m&igrave;nh. Mọi người h&atilde;y tiếp tục theo d&otilde;i c&aacute;c b&agrave;i tiếp theo v&agrave; cập nhật c&aacute;c b&agrave;i mới nhất tr&ecirc;n <a href="http://tek4.vn">tek4</a> nh&eacute;!</p> <p>P/s: Cảm ơn mọi người đ&atilde; tin tưởng tek4!</p>