Hỏi - đáp Nơi cung cấp thông tin nghề nghiệp và giải đáp những thắc mắc thường gặp của bạn

10 lỗi các dev NodeJS thường mắc phải - Phần 1

Kể từ khi ra mắt thế giới lập trình, Node.js đã nhận những phản ứng trái chiều, có khen lẫn chê. Có vẻ như tranh cãi vẫn còn tiếp diễn. Nhưng trong những cuộc tranh cãi này, chúng ta nhận ra mỗi ngôn ngữ lập trình và mỗi platform đều bị chỉ trích vì vài vấn đề nào đó xung quanh cách thức chúng ta sử dụng platform. Dù cho Node.js khiến dev khó viết code an toàn hơn hay viết concurrent code chất lượng cao, thì platform này vẫn nổi tiếng vì đã xây dựng được các dịch vụ web mạnh mẽ và hiện đại. Các dịch vụ web này scale tốt và đã chứng minh được tính ổn định, duy trì được vị trí trên Internet.

Tuy nhiên, cũng giống như bất kì platform nào khác, Node.js lại dễ “tổn thương” trước những vấn đề lập trình. Một số vấn đề làm suy giảm hiệu suất, một số khác khiến Node.js không thể sử dụng được dù bạn đã rất cố gắng. Trong bài viết này, chúng tôi sẽ điểm qua 10 lỗi mà các lập trình viên Node.js mới thường mắc phải, và cách phòng trách để trở thành 1 dev Node.js pro.

Lỗi #1: Ngăn event loop

JavaScript trong Node.js (tương tự như trình duyệt) cung cấp môi trường chạy single thread. Đồng nghĩa là sẽ không có 2 phần nào trong ứng dụng của bạn chạy song song, thay vào đó, bạn sẽ có cơ chế concurrency bằng cách giải quyết các I/O bound operations 1 cách riêng biệt, bất đồng bộ. Ví dụ, 1 request từ Node.js đến cơ sở dữ liệu để lấy 1 vài tài liệu nào đó sẽ cho phép Node.js tập trung vào những phần khác của ứng dụng.

1
2
3
4
5
6
 
// Trying to fetch an user object from the database. Node.js is free to run other parts of the code from the moment this function is invoked..
db.User.get(userId, function(err, user) {
// .. until the moment the user object has been retrieved here
})
 

node.js single threaded environment

Tuy nhiên, 1 phần code CPU-bound trong 1 instance Node.js với hàng ngàn client được kết nối sẽ ngăn event loop, sẽ để tất cả các client phải chờ. Các code CPU-bound bao gồm cách thức để sắp xếp 1 array lớn, chạy 1 loop cực dài… Ví dụ như:

Nếu bạn chạy trên array “users” nhỏ, bạn có thể gọi được hàm “sortUsersByAge” này, nhưng với 1 array lớn, nó sẽ có ảnh hưởng xấu đến hiệu năng chúng. Nếu bạn bắt buộc phải hoàn thành nó, thì bạn chắc chắn sẽ không có thứ gì khác đợi trong event loop (ví dụ: nếu đây là 1 phần của command-line tool mà bạn đang xây dựng với Node.js, và sẽ không có vấn đề gì nếu chạy riêng biệt toàn bộ), thì đây có thể không phải là vấn đề. Tuy nhiên, trong 1 server instance Node.js đang cố gắng phục vụ hàng ngàn users cùng 1 lúc thì pattern như thế có thể là hiểm họa.

Nếu array users này đang bị lấy lại từ cơ sở dữ liệu thì cách giải quyết lý tưởng sẽ là lấy array đã được phân loại sẵn trực tiếp từ cơ sở dữ liệu. Nếu event loop đang bị khóa bởi 1 loop được viết để tính toán lịch sử dữ liệu giao dịch tài chính dài thì nó có thể hoãn vài thiết lập worker/ queue bên ngoài để không cắt xén event loop.

Như bạn có thể thấy, không có giải pháp để giải quyết nhanh vấn đề này của Node.js, chỉ còn cách giải quyết từng trường hợp riêng biệt. Ý tưởng căn bản là không thực hiện các hoạt động nặng CPU trong các instances Node.js mặt trước – những instances mà các khách hàng kết nối đồng thời.

Lỗi #2: Gọi callback hơn 1 lần

JavaScript phụ thuôc vào callbacks từ rất lâu rồi. Trong các trình duyệt web, các events được giải quyết bằng cách đưa các tham chiếu đến các hàm (thường là vô danh) hoạt động như các callbacks. Trong Node.js, các callbacks từng là các yếu tố không đồng bộ duy nhất trong code có thể giao tiếp với nhau – cho đến khi Promise ra đời. Callbacks vẫn được dùng và các developers package vẫn thiết kế các APIs của mình xung quanh các callbacks. Một vấn đề Node.js thường thấy liên quan đến việc sử dụng các callbacks là gọi chúng nhiều hơn 1 lần. Thông thường, 1 hàm do 1 package cung cấp để thực hiện 1 hành động nào đó không đồng bộ được thiết kế để 1 hàm hoạt động như 1 đối số cuối cùng của nó – hàm này sẽ được gọi khi nhiệm vụ không đồng thời đã hoàn thành:

Lưu ý rằng cách thức để có 1 lệnh trả lại mỗi lần gọi “done” cho đến thời điểm cuối cùng, bởi vì gọi callback không tự động kết thúc quy trình thực hiện của hàm hiện tại. Nếu “trả lại” đầu tiên được ghi chú bên ngoài, thì khi chuyển 1 mật khẩu không dãy vẫn khiến “computeHash” bị gọi. Tùy vào cách “computeHash” giải quyết tình huống đó, mà “done” có thể bị gọi nhiều lần. Có thể bắt gặp các dev bất kì đang sử dụng hàm này từ bất kì nơi nào khi call mà dev chuyển bị gọi nhiều lần.

Chỉ cần cẩn thận, dev có thể tránh được lỗi Node.js. Một số lập trình viên Node.js theo thói quen thêm 1 từ khóa trả về mỗi lần gọi callback:

Trong các hàm không đồng bộ, giá trị trả về hầu như không có ý nghĩa gì nhiều, nên cách giải quyết này tránh được vấn đề như thế.

Lỗi #3: Nhồi nhét Callbacks

Các callbacks bị nhồi nhét sâu không được xem là vấn đề Node.js nữa mà thường được gọi là “địa ngục callback” (callback hell). Tuy nhiên, hành động này có thể khiến bạn không thể kiểm soát được dòng code:

Task càng khó khăn, thì vấn đề càng trở nên nghiêm trọng. Nhồi nhét các callbacks như thế dễ dẫn đến hậu quả là code dễ bị lỗi, khó đọc, khó duy trì. Cách giải quyết là làm rõ các tasks thành các hàm nhỏ, rồi kết nối chúng lại. Tuy nhiên, một trong những cách được cho là gọn gàng nhất là sử dụng packages Node.js tiện dụng để giải quyết các patterns Javascript không đồng bộ, như Async.js:

Tương tự như “async.waterfall”, có 1 số hàm khác do Async.js cung cấp để giải quyết các patterns không đồng bộ khác biệt. Một cách ngắn gọn, chúng tôi đã sử dụng các ví dụ đơn giản hơn ở đây, nhưng thực tế thường tồi tệ hơn.

Lỗi #4: Mong muốn các callbacks chạy đồng bộ

Lập trình không đồng bộ (async) với các callbacks có thể không có gì đặc biệt với JavaScript và Node.js, nhưng chúng là lý do dẫn đến sự nổi tiếng của ngôn ngữ này. Với các ngôn ngữ lập trình khác, chúng ta đã quen với việc dự đoán được thứ tự execution khi 2 lệnh thực hiện lần lượt, nếu không có instruction đặc biệt chen vào giữa các câu lệnh. Khi đó, các câu lệnh điều kiện, các câu lệnh loop và các yêu cầu hàm sẽ bị giới hạn.

Tuy nhiên, trong JavaScript, với các callbacks, 1 hàm cụ thể có thể sẽ chạy không tốt nếu task đang đợi chưa được hoàn thành. Execution của hàm hiện tại sẽ chạy cho đến khi kết thúc mà không bị ngừng lại:

Lưu ý, gọi hàm “testTimeout” đầu tiên sẽ print “Begin”, sau đó print “Waiting..” cùng với tin nhắn “Done!” sau đó 1 giây.

Sau khi 1 callback đã bị loại bỏ, bạn cần phải gọi từ trong nó để thực hiện 1 hành động khác.

Lỗi #5: Chỉ định “exports”, thay vì “module.exports”

Trong Node.js, mỗi file là 1 module nhỏ và độc lập. Nếu package của bạn có 2 files, thì có thể “a.js” và “b.js”, sau đó để “b.js” tiếp cận các functions “a.js”’s, “a.js” phải xuất nó ra bằng cách thêm các properties đến đối tượng xuất:

Sau khi thực hiện xong, bất kì dev nào muốn gọi “a.js” sẽ được nhận 1 đối tượng với hàm property “verifyPassword”:

Tuy nhiên, sẽ ra sao nếu bạn muốn xuất trực tiếp hàm này,và không phải là property của đối tượng khác? Chúng ta có thể ghi đè lên các exports để làm việc này, nhưng chúng ta không nên xem nó là global variable:

Lưu ý cách thức chúng ta đang xem “exports” là 1 property của đối tượng module. Sự khác biệt ở đây, giữa “module.exports” và “exports” là rất quan trọng. Đây cũng thường là nguyên nhân khiến các lập trình viên Node.js mới bực bội, khó chịu.

Via Techtalk.vn