Để đọc dữ liệu người dùng nhập trong Node.js, hãy tạo một interface readline trên process.stdin và đặt câu hỏi bằng rl.question(). Node.js không có prompt(): phiên bản của trình duyệt mở một hộp thoại, còn chương trình terminal thì không có hộp thoại nào. Cách viết hiện đại dùng readline/promises, trong đó await rl.question('Name? ') trả về dòng mà người dùng đã gõ. Hãy đóng interface khi xong việc, nếu không chương trình sẽ cứ chờ thêm dữ liệu.
question() in ra lời nhắc, chờ một dòng dữ liệu rồi trả về dòng đó (đã bỏ ký tự xuống dòng), nên chương trình in ra Next year you will be 37. Với nhiều câu hỏi, hãy lần lượt await từng question().
question() của readline với callback
API gốc, có trong mọi phiên bản Node.js, truyền câu trả lời cho một callback. Câu hỏi lồng nhau thì callback cũng lồng nhau:
Chương trình in ra lời nhắc, rồi You picked blue. Câu trả lời là một chuỗi bình thường, nên hãy trim trước khi so sánh: nếu người dùng gõ thêm một dấu cách sau từ đó, bạn sẽ nhận được 'blue ', không bằng 'blue'.
Đọc dữ liệu từng dòng
Để xử lý từng dòng cho đến khi hết dữ liệu, hãy duyệt interface bằng for await. Đây là cách một chương trình đọc danh sách người dùng gõ vào (kết thúc bằng Ctrl+D) hoặc một tệp được pipe vào bằng node index.js < numbers.txt. Code cũ làm việc tương tự bằng sự kiện, rl.on('line', fn) cho mỗi dòng và rl.on('close', fn) ở cuối; vòng lặp chính là cặp sự kiện đó được viết từ trên xuống dưới:
Kết quả in ra là 3 numbers, total 49, và vòng lặp tự kết thúc khi hết dữ liệu, nên không cần close(). Thêm dòng dữ liệu thứ tư là 1 thì kết quả thành 4 numbers, total 50.
Đọc toàn bộ stdin một lần
Với dữ liệu được pipe từ một chương trình khác (cat data.txt | node index.js), đọc tất cả trong một lần gọi sẽ đơn giản hơn. File descriptor 0 là đầu vào chuẩn (standard input). Một tệp được lưu trên Windows có thể kết thúc mỗi dòng bằng \r\n, nên hãy tách theo /\r?\n/ khi dữ liệu có thể đến từ đó:
Chương trình in ba mặt hàng thành một cột. readFileSync(0) chặn cho đến khi nhận đủ dữ liệu, điều này ổn với script đọc từ pipe và với việc kiểm thử, nhưng không phù hợp cho công cụ tương tác, vì người dùng sẽ phải gõ hết mọi thứ trước khi thấy bất kỳ lời nhắc nào.
Nên dùng cách nào
Cả bốn cách đều đọc cùng một đầu vào chuẩn. Khác biệt nằm ở thời điểm code của bạn nhận được văn bản: từng câu trả lời, từng dòng, hay tất cả ở cuối.
| Nhu cầu | Dùng |
|---|---|
| Hỏi vài câu theo thứ tự | readline/promises với await rl.question() |
| Hỗ trợ các phiên bản Node.js trước 17 | readline với callback question() |
| Xử lý từng dòng ngay khi nhận được | for await (const line of rl) |
| Đọc toàn bộ pipe hoặc tệp từ stdin | fs.readFileSync(0, 'utf8') |
| Giá trị đã biết trước khi chương trình chạy | process.argv |
Lỗi thường gặp
Quên rl.close(). Interface giữ đầu vào chuẩn luôn mở, nên chương trình không bao giờ thoát sau câu hỏi cuối cùng. Hãy đóng nó khi đã có đủ câu trả lời.
Dùng câu trả lời như một số. Giống mọi thứ được gõ trong terminal, câu trả lời là chuỗi. '36' + 1 là '361'; hãy chuyển đổi bằng Number() trước.
Pipe nhiều câu trả lời vào nhiều câu hỏi. Khi dữ liệu đến từ pipe thay vì bàn phím, readline đọc tất cả cùng lúc, và dòng nào đến trước khi question() tương ứng được hỏi sẽ bị bỏ mất. Với dữ liệu từ pipe, hãy dùng vòng lặp for await hoặc readFileSync(0).
Bỏ sót tùy chọn output. Không có output: process.stdout, question() sẽ không bao giờ in ra lời nhắc, và người dùng chỉ nhìn chằm chằm vào một terminal trống. Vòng lặp đọc từng dòng thì không cần tùy chọn này, vì nó không in gì cả.
Câu hỏi thường gặp
Vì sao prompt không được định nghĩa trong Node.js?
prompt() là một hàm của trình duyệt dùng để mở hộp thoại, mà Node.js không có cửa sổ nào để mở nó. Trong Node.js, hãy dùng readline, hoặc một package như prompts hay inquirer để có menu và ô nhập mật khẩu.
Làm sao đọc mật khẩu mà không hiển thị nó?
readline hiển thị lại mọi phím được gõ. Các package như prompts hay @inquirer/password sẽ che dữ liệu nhập; nếu tự làm, bạn phải chuyển terminal sang raw mode bằng process.stdin.setRawMode(true) và tự xử lý từng phím.
Nên dùng readline hay process.argv?
Dùng process.argv khi các giá trị đã biết trước khi chương trình chạy, để nó có thể chạy trong một script mà không cần ai ngồi chờ. Dùng readline cho các công cụ tương tác hỏi lần lượt từng câu.