Menu

readline trong Node.js: đọc dữ liệu nhập từ terminal

Node.js không có hàm prompt(). Để hỏi người dùng trong terminal, hãy tạo một interface readline trên process.stdin rồi gọi question(), hoặc await nó với readline/promises.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Để đọ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ầuDù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 17readline với callback question()
Xử lý từng dòng ngay khi nhận đượcfor await (const line of rl)
Đọc toàn bộ pipe hoặc tệp từ stdinfs.readFileSync(0, 'utf8')
Giá trị đã biết trước khi chương trình chạyprocess.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.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu