Node.js Generators & So sánh với lệnh gọi lại
⚡ Tóm tắt thông minh
GeneratorTrong Node.js, `yield` là các hàm mà quá trình thực thi của chúng có thể bị tạm dừng và tiếp tục theo yêu cầu. Từ khóa `yield` tạm dừng hàm, phương thức `next` tiếp tục hàm, và sự kết hợp này giúp đơn giản hóa các hàm gọi lại lồng nhau thành mã tuần tự dễ đọc.

Trong hướng dẫn này, chúng ta sẽ xem xét các generator và sự khác biệt giữa chúng với các hàm callback.
Máy phát điện là gì?
Generatortrở nên phổ biến vào năm Node.js Bởi vì những gì họ có khả năng làm được.
- Generatorlà các hàm thực thi có thể tạm dừng và tiếp tục ở thời điểm sau.
- GeneratorCác câu lệnh `s` rất hữu ích cho các khái niệm như "thực thi lười biếng". Bằng cách tạm dừng thực thi và tiếp tục khi cần, các giá trị chỉ được lấy khi chúng cần thiết.
GeneratorCó hai phương pháp chính dưới đây.
- Phương pháp năng suất — được gọi bên trong một hàm để dừng thực thi tại chính xác dòng lệnh nơi
yieldxuất hiện. - Phương pháp tiếp theo — được gọi từ ứng dụng chính để tiếp tục một hàm đã tạm dừng. Quá trình thực thi tiếp tục cho đến lần gọi tiếp theo.
yieldhoặc cho đến khi kết thúc hàm.
Hãy xem một ví dụ về cách sử dụng generator. Ví dụ này định nghĩa một hàm cộng đơn giản, thực hiện phép cộng hai số, và dừng thực thi tại các điểm khác nhau để minh họa cách generator hoạt động.
function* Add(x) { yield x + 1; // The value passed into the second next() call lands here var y = yield(null); return x + y; } var gen = Add(5); gen.next(); // runs to the first yield, returns { value: 6, done: false } gen.next(6); // resumes, y becomes 6, returns { value: 11, done: true }
Code Giải thích
- Bước đầu tiên là định nghĩa hàm sinh. Lưu ý rằng
*được thêm vàofunctiontừ khóa. Hàm được gọi làAddvà nhận một tham sốx. -
yieldTừ khóa này dành riêng cho các hàm tạo và tạm dừng hàm giữa chừng khi đang thực thi. Quá trình thực thi dừng lại ở đây cho đến khinext()Được gọi. - Thứ hai
yieldvừa tạm dừng hàm, vừa nhận một giá trị: bất cứ giá trị nào được truyền cho hàm tiếp theo.next()cuộc gọi trở thành giá trị củay. - đang gọi
gen.next()Lần đầu tiên chạy hàm này, nó sẽ thực thi cho đến lần đầu tiên.yield. Gọigen.next(6)tiếp tục, giao 6 choyvà trả lạix + y.
⚠️ Đính chính: Danh sách ban đầu có chứa dòng y = 6 ngay sau đó var y = yield(null);Việc gán giá trị đó sẽ ghi đè lên bất kỳ giá trị nào khác. next() nguồn cung cấp, điều này làm mất đi mục đích của ví dụ và khiến cho ví dụ thứ hai trở nên vô nghĩa. yield Vô nghĩa. Giá trị hiện được truyền vào thông qua gen.next(6)Đây chính là cách mà máy phát điện nhận dữ liệu từ người gọi.
Hàm gọi lại so với hàm tạo
GeneratorGiải pháp này giúp giải quyết vấn đề được gọi là "địa ngục callback". Trong quá trình phát triển ứng dụng Node.js, các hàm callback đôi khi lồng nhau quá sâu đến mức khó theo dõi.
Đây là lúc các trình tạo (generator) phát huy tác dụng. Một trong những ví dụ phổ biến nhất là tạo các hàm hẹn giờ. Ví dụ dưới đây xây dựng một hàm trễ thời gian đơn giản và sau đó gọi nó với các độ trễ là 1000, 2000 và 3000 ms.
Bước 1) Hãy định nghĩa hàm gọi lại với mã xử lý độ trễ thời gian cần thiết.
function Timedelay(ptime, callback) { setTimeout(function() { callback("Pausing for " + ptime); }, ptime); // the delay must use ptime, the parameter name }
Code Giải thích
- Một chức năng được gọi là
Timedelayđược tạo với một tham sốptime, trong đó ghi rõ số mili giây cần tạm dừng. setTimeoutChờ bấy nhiêu mili giây rồi gọi hàm callback được cung cấp.
⚠️ Đính chính: mã gốc đã được thông qua time như lập luận thứ hai cho setTimeout, nhưng không có biến nào được đặt tên time tồn tại — tham số là ptimeNhư đã viết, nó ném ra ReferenceError: time is not defined.
Bước 2) Bây giờ hãy xem xét đoạn mã sử dụng hàm gọi lại (callback). Để tạo chuỗi độ trễ 1000, 2000 và 3000 mili giây, các hàm gọi lại sẽ lồng vào nhau.
Timedelay(1000, function(message) { console.log(message); Timedelay(2000, function(message) { console.log(message); Timedelay(3000, function(message) { console.log(message); }); }); });
Code Giải thích
Timedelayđược gọi như một hàm callback với giá trị là 1000.- Bên trong hàm gọi lại đó,
Timedelayđược gọi lại với số 2000. - Bên trong cái đó,
Timedelayđược gọi lần thứ ba với số 3000.
⚠️ Đính chính: mỗi lần gọi lại trong nhật ký gốc msg, nhưng tham số được đặt tên là messageCả ba tuyến đều ném bóng. ReferenceError: msg is not defined.
Từ đoạn mã trên, bạn có thể thấy nó trở nên phức tạp hơn với mỗi lần gọi thêm — sự lồng ghép này chính là điều mà "địa ngục callback" mô tả.
Bước 3) Bây giờ hãy xem hành vi tương tự được viết bằng các hàm tạo. Chuỗi này trông giống như ba dòng thẳng chứ không phải ba cấp độ lồng nhau.
function* Messages() { console.log(yield Timedelay(1000)); console.log(yield Timedelay(2000)); console.log(yield Timedelay(3000)); }
Code Giải thích
- Một hàm tạo được định nghĩa để gọi
Timedelaychức năng. yieldđược sử dụng vớiTimedelayvà 1000 là giá trị tham số.yieldđược sử dụng lại với 2000, và một lần nữa với 3000.
⚠️ Đính chính: người viết ban đầu đã viết console,log(...) Thay vì dấu chấm, cả ba dòng đều có dấu phẩy, đây là lỗi cú pháp và khiến tệp không thể được phân tích cú pháp.
Làm thế nào để chạy một Generator với chức năng Runner
Đoạn mã máy phát ở trên đọc rất hay nhưng sẽ không tự hoạt động. Đây là chi tiết mà hầu hết các lời giải thích đều bỏ qua, và đó là lý do tại sao phiên bản đơn giản có thể trông giống như phép thuật. Máy phát là một cỗ máy tạm dừng, và phải có thứ gì đó liên tục nhấn nút. Gọi Messages() Nó chỉ đơn giản tạo ra đối tượng generator và trả về ngay lập tức; không có bộ đếm thời gian nào được bắt đầu và không có gì được in ra.
Mảnh ghép còn thiếu là một hàm chạy: một hàm nhỏ gọi next(), chờ cho công việc bất đồng bộ hoàn thành, rồi gọi next() Lại một lần nữa với kết quả.
// Timedelay now hands its result back to the runner function Timedelay(ptime) { return function(resume) { setTimeout(function() { resume("Pausing for " + ptime); }, ptime); }; } // The runner drives the generator to completion function run(generatorFunction) { var it = generatorFunction(); function step(value) { var result = it.next(value); if (result.done) return; // result.value is the function returned by Timedelay result.value(step); } step(); } run(Messages);
Có ba điểm làm cho điều này hiệu quả. Thứ nhất, Timedelay Nó không còn nhận trực tiếp một hàm gọi lại nữa; nó trả về một hàm mà trình chạy có thể gọi, trả lại quyền điều khiển khi bộ hẹn giờ kích hoạt. Thứ hai, it.next(value) đưa kết quả trước đó vào máy phát đang tạm dừng, đó là điều cho phép yield Thứ ba, người chạy dừng lại ngay khi result.done Khi điều đó trở thành sự thật, quá trình đệ quy sẽ tự nhiên kết thúc. Mô hình runner này chính xác là những gì các thư viện như co đã cung cấp, và đó là cơ chế giúp... async/await sau đó được hấp thụ vào chính ngôn ngữ.
GeneratorNên sử dụng `s` hay `async/await`: Nên dùng cái nào?
GeneratorTrước đây, viết mã bất đồng bộ dạng phẳng là cách viết tiêu chuẩn. async/await đã tồn tại, và cả hai đều có chung ý tưởng cơ bản là tạm dừng một hàm ở giữa chừng. Node đã hỗ trợ async/await Tính năng này được hỗ trợ mặc định từ phiên bản 7.6 trở đi, điều này làm thay đổi khuyến nghị dành cho bất kỳ ai bắt đầu công việc mới ngày hôm nay.
| Yếu tố | Generators | không đồng bộ/đang chờ |
|---|---|---|
| Cần người chạy việc vặt | Vâng, do bạn hoặc thư viện viết. | Không, nó được tích hợp sẵn trong ngôn ngữ. |
| Từ khóa tạm dừng | yield |
await |
| Xử lý lỗi | Người chạy phải chuyển tiếp lỗi | Cố gắng bắt giữ thông thường |
| Có thể tạm dừng giữa chừng | Vâng, và có thể tiếp tục một cách chọn lọc. | Không, chạy đến khi hoàn thành |
| Tốt nhất cho | Chuỗi lười biếng và dòng vô hạn | Công việc bất đồng bộ thông thường |
Sử dụng async/await Đối với luồng bất đồng bộ trong mã mới, vì nó loại bỏ bộ chạy và khôi phục lại khối try và catch thông thường. Giữ lại các generator cho những việc mà chỉ chúng mới làm tốt: tạo ra các giá trị một cách lười biếng, lặp lại các chuỗi lớn hoặc vô hạn, và bất kỳ trường hợp nào mà người tiêu dùng quyết định chính xác khi nào giá trị tiếp theo được tính toán.
Chung Generator Các lỗi và cách khắc phục chúng
Hầu hết các sự cố của hàm tạo xuất phát từ việc quên rằng hàm này không hoạt động cho đến khi có lệnh gọi. next()hoặc do những lỗi cú pháp nhỏ xung quanh dấu sao. Mỗi triệu chứng dưới đây đều nêu rõ nguyên nhân và cách khắc phục.
- Không có gì xảy ra khi trình tạo được gọi: Việc gọi hàm này trả về một iterator, chứ không phải kết quả. Hãy gọi
next()hoặc chuyền bóng cho người chạy. - Lỗi cú pháp khi thực hiện lệnh yield: Dấu hoa thị bị thiếu.
yieldchỉ có hiệu lực bên trong mộtfunction*. - Giá trị từ next() nhận được là undefined: đầu tiên
next()Cuộc gọi không thể trả về giá trị vì trình tạo chưa đạt đến một giá trị nào.yieldTuy nhiên. Hãy gửi dữ liệu từ cuộc gọi thứ hai trở đi. - Vòng lặp vô tận trong trò chơi chạy:
result.donekhông bao giờ được kiểm tra, vì vậynext()vẫn tiếp tục được gọi sau khi kết thúc. Luôn trả về khi hoàn thành là đúng. - Các lỗi tự biến mất một cách lặng lẽ: Một lỗi từ chối bên trong bước bất đồng bộ sẽ không bao giờ được truyền ngược trở lại trình tạo. Hãy chuyển tiếp nó bằng
it.throw(err)để khối try-catch được viết bên trong hàm tạo có thể thực sự nhìn thấy và xử lý nó.




