NodeJS Q&A

MVT
Đang cập nhật

Tổng quan

Ngoài những câu hỏi thông thường về kỹ năng, bằng cấp, kinh nghiệm... Nhà tuyển dụng còn yêu cầu một lập trình viên cần phải có những kiến thức chuyên sâu liên quan đến một lĩnh vực cụ thể mà họ mong muốn, nếu bạn là một lập trình viên liên quan đến javascript, chắc hẳn bạn không thể không biết về `Node.js.

Node.js ra đời từ sau khi V8 engine được Google phát triển năm 2008. Đến ngày nay, nó thực sự đã trở thành một thế lực với cộng đồng developer đông đảo cũng như có rất nhiều start-up sử dụng nó.

Tuy nhiên, bất kể kinh nghiệm ra sao, những câu hỏi quan trọng nhất để hỏi các nhà phát triển Node.js trong cuộc phỏng vấn xoay quanh những điều sau :

  • Node.js hoạt động như thế nào?
  • Tại sao lại lựa chọn Node.js để phát triển ứng dụng
  • Debug cho ứng dụng Node.js bằng cách nào?
  • Những thách thức trong qúa trình phát triển bằng Node.js

Một số thuật ngữ

I/O (input/output)

I/O chủ yếu đề cập đến sự tương tác của chương trình thông qua ổ đĩa và mạng của hệ thống. Ví dụ về hoạt động I/O bao gồm đọc/ ghi dữ liệu từ vị trí nào đến vị trí nào đó trong ổ đĩa, thực hiện các yêu cầu HTTP và giao tiếp với cơ sở dữ liệu. Chúng rất chậm so với việc truy cập bộ nhớ (RAM) hoặc thực hiện công việc trên CPU.

Synchronous vs Asynchronous (Đồng bộ vs bất động bộ)

Quá trình thực thi đồng bộ (synchronous hay sync)

thường đề cập đến code thực thi theo trình tự. Trong lập trình đồng bộ, chương trình được thực hiện từng dòng, từng dòng một. Mỗi lần hàm được gọi, chương trình thực thi sẽ chờ cho đến khi hàm này trả về một kết quả nào đó trước khi nó thực hiện những dòng code tiếp theo.

Quá trình thực thi bất đồng bộ (asynchronous hay async)

đề cập đến quá trình thực thi không theo trình tự của các dòng code. Trong lập trình bất đồng bộ, chương trình không cần phải chờ một hàm hay một tác vụ nào đó hoàn tất mà nó có thể đi tiếp đến tác vụ tiếp theo.

Trong ví dụ sau, chương trình thực thi đồng bộ giúp alert được thực thi theo trình tự. Trong hoạt với bất đồng bộ, alert(2) dường như thực thi thứ hai, nhưng không.

// Synchronous: 1,2,3
alert(1);
alert(2);
alert(3);

// Asynchronous: 1,3,2
alert(1);
setTimeout(() => alert(2), 0);
alert(3);

Biểu thức không đồng bộ thường liên quan đến I/O, mặc dù setTimeout là một ví dụ không phải là I/O nhưng vẫn bất đồng bộ. Nói chung, bất kỳ điều gì liên quan việc xử lý tính toán là đồng bộ, còn bất kỳ điều gì liến quan đến input/output/timing là bất đồng bộ. Lý do mà I/O được thực hiện bất đồng bộ là vì chúng xử lý rất chậm và đôi khi bằng một cách nào đó chúng có thể chặn quá trình thực thi code.

Blocking vs Non-blocking

Blocking đề cập đến hoạt động chặn không cho chương trình thực thi đến các dòng code tiếp theo cho đến khi hoạt động đó kết thúc trong khi non-blocking đề cập đến việc hoạt động không chặn quá trình thực thi. Theo trang chủ của Node.js, blocking là khi việc thực thi JavaScript bổ sung trong quy trình Node.js phải đợi cho đến khi hoàn tất một thao tác không phải JavaScript.

Các phương thức blocking thực thi đồng bộ trong khi các phương thức non-blocking thực thi không đồng bộ.

// Blocking
const fs = require('fs');
const data = fs.readFileSync('/file.md'); // blocking tại đây cho đến khi đọc xong file
console.log(data);
moreWork(); // will run after console.log

// Non-blocking
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
  console.log(data);
});
moreWork(); // will run before console.log

Trong ví dụ đầu tiên ở trên, console.log sẽ được gọi trước moreWork().

Trong ví dụ thứ hai, fs.readFile()non-blocking nên việc thực thi JavaScript có thể tiếp tục và moreWork () sẽ được gọi đầu tiên.

Trong Node, non-blocking chủ yếu đề cập đến hoạt động nhập xuất (input/output). Với JS, nó thể hiện hiệu suất kém do sử dụng nhiều CPU thay vì chờ đợi một hoạt động không phải JavaScript, chẳng hạn như I/O, thường không được blocking.

Tất cả các phương thức I/O trong thư viện chuẩn Node.js đều cung cấp các phiên bản bất đồng bộ, đây là các non-blocking và chấp nhận các hàm callback. Một số phương thức cũng có liên hệ với blocking và có tên kết thúc bằng Sync.

Hoạt động I/O Non-blocking cho phép một quy tirình duy nhất phục vụ nhiều yêu cầu cùng một lúc. Thay vì quy trình xử lý bị chặn và chờ cho I/O xử lý xong, thì các hoạt động này ủy quyền cho hệ thống, vì vậy quá trình có thể thực thi đoạn code tiếp theo. Hoạt động I/O Non-blocking cấp một hàm gọi lại (callback function) để gọi khi hoạt động hoàn tất.

Callback

Một callback là một hàm được truyền vào như một tham số của hàm khác, sau đó nó được gọi bên trong hàm chứa nó để hoàn thành một số loại hành động vào một thời điểm thuận tiện. Có hai thời điểm callback được gọi, một là gọi ngay lập tức (sync callback) hoặc chờ cho một tác vụ nào đó xử lý xong rồi mới gọi (async callback)

// Sync callback
function greetings(callback) {
  callback();
}
greetings(() => { console.log('Hi'); });
moreWork(); // will run after console.log

// Async callback
const fs = require('fs');
fs.readFile('/file.md', function callback(err, data) { // fs.readFile is an async method provided by Node
  if (err) throw err;
  console.log(data);
});
moreWork(); // will run before console.log

Trong ví dụ đầu tiên, hàm gọi lại được gọi ngay trong hàm greetings() bên ngoài được gọi và log ra truớc khi moreWork() tiến hành.

Trong ví dụ thứ hai, fs.readFile (một phương thức không đồng bộ do Node cung cấp) đọc tệp và khi kết thúc, nó gọi hàm callback nếu như không có lỗi. Trong thời gian chờ đợi, chương trình có thể tiếp tục thực thi code.

Một async callback có thể được gọi khi một sự kiện xảy ra hoặc khi một nhiệm vụ hoàn thành. Nó ngăn chặn không cho phép các code khác được thực thi trong thời gian chờ đợi.

Thay vì đọc code từ trên xuống dưới theo thủ tục, các chương trình bất đồng bộ có thể thực thi các chức năng khác nhau tại các thời điểm khác nhau dựa trên thứ tự và tốc độ mà các chức năng trước đó như gửi request đến http hoặc quá trình file hệ thống đọc dữ liệu diễn ra. Chúng được sử dụng khi bạn không biết khi nào một số thao tác bất đồng bộ sẽ hoàn tất.

Bạn nên tránh sử dụng các callback function lồng nhau, hay gọi là callback hell vì sau nó khi đọc lại code nó rất khó sử dụng, bảo trì hoặc phát triển thêm.

Events và event-driven programing (Lập trình sự kiện và hướng sự kiện)

Event là những sự kiện hay hoạt động được tạo ra bởi người dùng hoặc từ hệ thống như click, mouseover, keyup, keydown... một quá trình tải xuống hoàn tất, hay một lỗi do phần cứng hoặc phần mềm

Event-driven programming (Lập trình hướng sự kiện) là một mô hình lập trình mà các luồng chương trình được xác định by các sự kiện ở đó. Một chương trình hướng sự kiện thực hiện các hành động để đáp ứng các sự kiện. Khi sự kiện xảy ra, nó liền gọi đến callback function.

Bây giờ, hãy cố gắng hiểu Node và xem tất cả những thứ này liên quan đến tôi như thế nào nhé.

Scalability

Scalability là khả năng của chương trình có khả năng mở rộng. VD nếu bạn có thể thực hiện truy vấn trên một cơ sở dữ liệu nhỏ (chẳng hạn dưới 1000 bản ghi), thì một chương trình có khả năng mở rộng cao vẫn hoạt động tốt trên tập dữ liệu nhỏ cũng như làm việc tốt trên tập dữ liệu lớn ( hàng triệu hay hàng tử bản ghi)

Node.js hoạt động như thế nào?

Node.js cho phép developers có thể sử dụng một ngôn ngữ lập trình (javascript) viết cho cả frontend và backend

Node.js sử dụng lập trình hướng sự kiện (event-driven programing) bằng mô hình async non-blocking, nơi mà có những hàm theo dõi các sự kiện được gọi đến và phản hồi các cuộc gọi đó thông qua callback function bất cứ khi nào các điều kiện đặt trước được đáp ứng. Vòng lặp sự kiện (event loop) quản lý chuỗi sự kiện.

Mô hình Node.js được xây dưng đơn luồng và vì vậy nó chỉ có thể gọi callback function trong một thời điểm. Tuy nhiên, mặc dù là đơn luồng, nhưng Node.js khá hiệu quả với việc xử lý nhiều hoạt động đồng thời. Vì hầu hết các hệ điều hiện đại đều đa luồng, nên Node.js cho phép nhân hệ điều hành xử lý các hoạt động I/O, thông qua thư viện libuv. Do đó, Node.js thừa hưởng được nhiều thuận lợi từ cả đơn luồng lẫn đa luồng trong cùng một thời điểm

Làm thế nào để bạn quyết định xem Node.js có phù hợp để xây dựng một loại ứng dụng cụ thể hay không?

Loại công nghệ được sử dụng để xây dựng ứng dụng có thể tạo ra tất cả sự khác biệt trong việc ứng dụng có đáp ứng đầy đủ mục đích của nó hay không.

Node.js sử dụng mô hình đơn luồng, thông thường, sẽ làm cho hiệu suất chậm hơn, đặc biệt là khi thực hiện nhiều hoạt động cùng một lúc. Tuy nhiên, Node đã xử lý vấn đề này bằng cách sử dụng mô hình async non-blocking, điều này cho phép thống có thể chạy đồng thời nhiều hoạt động khác nhau. Vòng lặp sự kiện (event-loop) giúp điều này trở nên khả thi.

Nói chung, Node.js phù hợp với việc phát triển của các ứng dụng yêu cầu trao đổi dữ liệu thời gian thực. Các loại ứng dụng cụ thể, với các ví dụ thực tế, có thể được xây dựng bằng Node.js bao gồm:

  • Ứng dụng Nhắn tin và Trò chuyện: ví dụ tốt nhất về trao đổi dữ liệu theo thời gian thực là các ứng dụng IM, cho dù để trao đổi văn bản, voice calls hay, video calls. Bản chất bất đồng bộ của Node.js thực thi theo thời gian thực làm cho nó phù hợp để xây dựng các ứng dụng giao tiếp.
  • Ứng dụng website thương mại điện tử E-commerce: nền tảng website thương mại điện tử đòi hỏi xử lý các request đồng thời theo thời gian thực và scale. Node.js giải quyết các vấn đề này bằng mô hình non-blocking. Trên thực tế, một trong hai yêu cầu chính thúc đẩy eBay đến với Node.js là khả năng xử lý các hoạt động liên kết I/O.
  • Ứng dụng stream : Netflix có lẽ là ứng dụng phát trực tuyến phổ biến nhất chạy trên Node.js. Ngoài nhu cầu trước đây là sử dụng cùng một ngôn ngữ lập trình cho cả phía máy chủ và máy khách, thì Node.js có một mô hình hiệu quả để xử lý các luồng, đây có lẽ là tính năng tốt nhất của runtime (thời gian thực thi).
  • Ứng dụng API: Ứng dụng API nhẹ và Node.js được thiết kế để phát triển ứng dụng nhẹ.
  • Microservices (Internet of things...) : Microservices phù hợp với mô hình Node.js của chương trình sự kiện một cách hiệu quả. Về cốt lõi, cả hai đều có hệ thống để xây dựng các ứng dụng có khả năng mở rộng cao

Làm như thế nào để debug ứng dụng trong Node.js?

Testing và Debuging là một phần tất yếu của phát triển phần mềm. Kiến thức về điều này rất quan trọng đối với bất kỳ nhà phát triển nào ở bất kỳ giai đoạn nào.

Quá trình gỡ lỗi ứng dụng Node.js thường bắt đầu bằng việc bật công tắc - kiểm tra. Sau đó, nó sử dụng cổng 127.0.0.1:9229 để tìm một ứng dụng khách gỡ lỗi trong đó có các tùy chọn nguồn mở khác nhau.

  • Node-Inspect là ứng dụng kiểm tra chính có sẵn theo mặc định trong môi trường Node.js. Nó là một trình gỡ lỗi CLI và được hỗ trợ bởi Node.js Foundation.
  • Chrome DevTools 55+ hoạt động cho mọi trình duyệt dựa trên Chromium bao gồm Google Chrome và Microsoft Edge cũng như các trình duyệt Brave và Opera. Định cấu hình chế độ gỡ lỗi thông qua chrome: // Inspect hoặc edge: // Kiểm tra (chỉ Edge)

Các ứng dụng khách bên thứ ba khác bao gồm Visual Studio, JetBrains, WebStorm, Gitpod, Eclipse IDE, v.v.

Một số hạn chế của việc sử dụng Node.js là gì? Và bạn sẽ vượt qua những thử thách như thế nào?

Không có ứng dụng, nền tảng hoặc công nghệ hoàn hảo. Biết được điểm yếu của bất kỳ công cụ nào (bao gồm cả Node.js) sẽ giúp nhà phát triển kiểm soát theo cách của họ thông qua các tính năng thực tế của chương trình.

Node.js sử dụng callback. Đôi khi, các hàm callback trở nên lồng vào nhau đến mức gần như không thể quản lý được. Điều này được biết đến là callback hell. Các nhà phát triển có thể đối phó với các tình huống như vậy bằng cách viết nhận xét bên cạnh các dòng code phức tạp để theo dõi chuỗi các lệnh callback. Một lựa chọn khác hiệu quả hơn là sử dụng Promises. Gần đây, giải phải hay nhất và tốt nhất mà JS đã phát hành từ ES2018 là async/await

Node.js được thiết kế cho hiệu suất nhẹ; nó có thể gặp sự cố nếu ứng dụng sử dụng nhiều tài nguyên CPU. Worker thread đã giúp giảm thiểu vấn đề này ở một mức độ nào đó bằng cách giới thiệu đa luồng cho Node.js. Worker cho phép chạy code đòi hỏi nhiều CPU trong một luồng song song khác với luồng chính. Được giới thiệu lần đầu tiên vào năm 2018 dưới dạng một tính năng thử nghiệm, các luồng Worker hiện đã được cải thiện rất nhiều, với v15.x

Một thách thức khác đối với Node.js là tính không ổn định của API của nó, thường xuyên thay đổi. Do thiếu khả năng tương thích ngược đối với nhiều thay đổi này, các nhà phát triển thường phải viết lại các phần code của họ với mỗi bản cập nhật.

Kết luận

Bên cạnh những câu hỏi kỹ thuật lập trình, tất nhiên, đừng bỏ qua những phần phỏng vấn liên quan đến kỹ năng và kinh nghiệm. Đặc biệt, hãy tìm kiếm những nhà phát triển có kinh nghiệm trong lĩnh vực thích hợp trong ngành của bạn. Sự phát triển của Node.js giống nhau ở mọi nơi nhưng khả năng ứng dụng khác nhau.