7 sai lầm thường gặp khi học Nodejs

Bài viết được sự cho phép của tác giả Sơn Dương

Kể từ thời điểm Node.js được công bố chính thức, nó đã chứng minh được giá trị của mình. Cũng vì có nhiều người quan tâm nên luôn có những cuộc tranh luận về Node.js. Có người khen, có người chê. Nhưng bạn có công nhận với mình là càng nhiều người ghét thì càng nhiều người sử dụng không? Càng nhiều người đâm đầu vào học Nodejs ^_^

Có một điều rằng, Node.js đang ngày càng phổ biến, được sử dụng ở nhiều ứng dụng lớn, hoặc rất lớn.

Tuy nhiên, để ứng dụng Node.js tốt theo đúng nghĩa thì nó phụ thuộc vào tài năng của người viết ra nó. Chứ một mình Node.js dù có tốt đến đâu thì cũng không thể “gánh team” được. Yếu tố con người luôn là yếu tố tiên quyết tới chất lượng sản phẩm.

Để sản phẩm có chất lượng kém thì dưới đây là 7 sai lầm khi học nodejs mà bạn nên mắc phải!

Sai lầm thường gặp khi học Nodejs

#Tạo vòng lặp dữ liệu lớn làm treo ứng dụng

Javascript trong Node.js (cũng giống như trong trình duyệt) là ngôn ngữ đơn luồng. Hay nói cách khác, ở cùng một thời điểm, chỉ có một luồng được thực thi, một task được thực hiện. Thay vào đó, để tăng hiệu suất ứng dụng, Node.js sẽ xử lý theo kiểu bất đồng bộ.

Ví dụ, truy xuất vào cơ sở dữ liệu từ ứng dụng Node.js để lấy dữ liệu. Trong lúc chờ cơ sở dữ liệu trả về kết quả thì Node.js có thể tranh thủ làm các task khác.

// 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

})

Tuy nhiên, không phải mã nào cũng bất đồng bộ, bản thân Javascript là ngôn ngữ đồng bộ, nên cũng giống như các ngôn ngữ khác.

Nếu một đoạn code mà cần thời gian dài để thực hiện có thể làm treo ứng dụng. Đặc biệt là các vòng lặp. Bạn có thể xem đoạn code bên dưới đây:

function sortUsersByAge(users) {

    users.sort(function(a, b) {

        return a.age < b.age ? -1 : 1

    })

}

Đây là hàm dùng để sắp xếp một mảng các users. Đây là hàm thuần túy đồng bộ, tức là phải làm xong hàm này thì mới thực hiện các hàm khác. Nếu mảng users có số lượng ít thì không sao. Nhưng nếu mảng users mà có rất nhiều phần tử thì sao?

Đấy là chưa kể ứng dụng Node.js còn phải phục vụ hàng ngàn khách truy cập nữa?

Đây chính là vấn đề, có thể làm sập server như chơi.

Để khắc phục vấn đề này thì có nhiều giải pháp, tùy vào hoàn cảnh ứng dụng. Ví dụ, nếu mảng users được lấy từ cơ sở dữ liệu (CSDL)  thì tốt nhất nên để CSDL tự sắp xếp trước khi trả về cho ứng dụng Node.js.

Về nguyên tắc là không nên để ứng dụng Node.js (ứng dụng trực tiếp xử lý request từ người dùng) phải thực hiện một tác vụ quá lâu.

  Xử lý ERROR trong NodeJS sao cho đúng?

  NodeJS version manager: Quản lý đa phiên bản NodeJS trên máy tính

#Sử dụng Return rỗng với Callback

Callback có lẽ là khái niệm mà chắc hẳn ai học Nodejs đều phải biết. Tuy nhiên, cách sử dụng như nào cho đúng thì mỗi người một cách. Trong Node.js, callback là cách để cách thành phần bất đồng bộ tương tác với nhau.

Một vấn đề phổ biến của Node.js mà các bạn developer ít kinh nghiệm hay mắc phải đó chính là gọi  Callback nhiều lần.

Thông thường, một API bất đồng bộ sẽ có một tham số cuối cùng là một hàm, được sử dụng task được thực hiện bởi API hoàn thành.

module.exports.verifyPassword = function(user, password, done) {
    if(typeof password !== ‘string’) {
        done(new Error(‘password should be a string’))
        return
    }

    computeHash(password, user.passwordHashOpts, function(err, hash) {
        if(err) {
            done(err)
            return
        }

        done(null, hash === user.passwordHash)
    })
}

Bạn nhìn đoạn code trên có thấy điều gì lạ không?

Tại sao lại phải sử dụng từ khóa return dưới mỗi callback vậy? Lý do họ làm như vậy vì khi gọi callback không làm kết thúc hàm, chương trình vẫn tiếp tục thực hiện các lệnh bên dưới. Đó là điều không đúng logic.

Tuy nhiên, nhìn việc return không có giá trị trả về cứ thế nào ấy. Mặc dù, trong các hàm kiểu bất đồng bộ thì việc return giá trị không có nhiều ý nghĩa lắm, ngoại trừ việc dùng return để kết thúc hàm.

Thay vào đó, chúng ta có  thể return chính hàm callback, nhìn code sẽ đẹp hơn nhiều.

if(err) {
    return done(err)
}

Tham khảo tuyển dụng nodejs lương cao trên TopDev

#Sử dụng Callback lồng nhau

Sử dụng Callback lồng nhau tới mức mất kiểm soát, người ta gọi là “callback hell”. Đây là một bad smell cực tệ với các ứng dụng Node.js, làm cho việc bảo trì mã nguồn trở nên vô vàn khó khăn. Mình cũng đã có hẳn một bài viết riêng về Callback hell và cách xử lý. Các bạn có thể tham khảo:  6 cách “trị” callback hell trong javascript

Ví dụ một callback lồng nhau:

function handleLogin(..., done) {
    db.User.get(..., function(..., user) {
        if(!user) {
            return done(null, ‘failed to log in)
        }
        utils.verifyPassword(..., function(..., okay) {
            if(okay) {
                return done(null, ‘failed to log in)
            }
            session.login(..., function() {
                done(null, ‘logged in)
            })
        })
    })
}

Có nhiều cách để xử lý callback lồng nhau, trong bài viết ở trên, mình có giới thiệu 6 cách để xử lý callback lồng nhau.

Nhân tiện bài viết này, mình giới thiệu thêm một cách nữa. Đó là sử dụng tiện ích Async.js:

function handleLogin(done) {
    async.waterfall([
        function(done) {
            db.User.get(..., done)
        },
        function(user, done) {
            if(!user) {
            return done(null, ‘failed to log in)
            }
            utils.verifyPassword(..., function(..., okay) {
                done(null, user, okay)
            })
        },
        function(user, okay, done) {
            if(okay) {
                return done(null, ‘failed to log in)
            }
            session.login(..., function() {
                done(null, ‘logged in)
            })
        }
    ], function() {
        // ...
    })
}

#Sử dụng Callback để chạy đồng bộ

Kỹ thuật bất đồng bộ với Callback tuy không phải là kỹ thuật chỉ có trên Javascript hay Node.js. Nhưng dường như sự phổ biến đã biến nó trở thành “tiêu chuẩn”. Giống như việc nói tới lập trình hướng đối tượng là người ta nghĩ ngay tới java vậy.

Với nhiều developer quen với các ngôn ngữ chạy đồng bộ như PHP, Java… Thì có xu hướng viết code đồng bộ trong Node.js. Và họ sử dụng Callback như một công cụ để viết code đồng bộ.

Tuy nhiên, Callback trong Javascript có thể không hoạt động như ý muốn của bạn.

Tham khảo đoạn code bên dưới:

function testTimeout() {
    console.log(“Begin”)
    setTimeout(function() {
        console.log(“Done!)
    }, duration * 1000)
    console.log(“Waiting..)
}

Như đoạn code trên thì màn hình sẽ in log theo thứ tự như sau, có vẻ không đúng thứ tự phải không?

'Begin'
'Waiting..'
'Done!'

Bất kể điều gì bạn muốn thực hiện sau callback đều phải được gọi bên trong hàm đó.

#Nhầm lẫn giữa “exports” và “module.exports”

Node.js coi mỗi một file javascript là một module nhỏ, độc lập với các file khác. Người ta gọi là tính đóng gói. Để một hàm trong file javascript có thể được sử dụng bởi file thì bạn cần phải export nó ra.

Giả sử trong package của bạn có 2 file javascript: one.js và two.js

Để two.js có thể gọi một hàm verifyPassword(...) trong one.js thì nó phải được export.

// one.js
exports.verifyPassword = function(user, password, done) { ... }

Khi bạn làm điều này, bất kể file nào require('one.js') đều có thể sử dụng hàm verifyPassword(...).

// two.js
require('one.js') // { verifyPassword: function(user, password, done) { ... } } 

Tuy nhiên, làm thế nào để export trực tiếp hàm verifyPassword() mà không phải định nghĩa nó như một thuộc tính của one.js? Câu trả lời là sử dụng module.export

// one.js
module.exports = function(user, password, done) { ... }

Nhìn có vẻ đơn giản vậy thôi nhưng sự khác nhau giữa exports và module.exports đang trở thành chủ đề bàn tán của rất nhiều Node.js developer.

#Throwing Errors bên trong Callbacks

Javascript cũng có Exception như bao ngôn ngữ khác như Java, C++. Bạn hoàn toàn có thể “Throw” hoặc try-catch một Exception.

Ví dụ như đoạn code dưới đây, try-catch sẽ bắt được Exception.

function slugifyUsername(username) {
    if(typeof username === ‘string’) {
        throw new TypeError(‘expected a string username, got '+(typeof username))
    }
    // ...
}

try {
    var usernameSlug = slugifyUsername(username)
} catch(e) {
    console.log(‘Oh no!)
}

Tuy nhiên, try-catch lại hoạt động không như mong đợi trong trường hợp của hàm bất đồng bộ.

Ví dụ, bạn muốn đoạn code an toàn, không bị crash khi có Exception, nên bạn thêm try-catch cho cả một đoạn code lớn. Mà trong đó có nhiều hàm bất đồng bộ. Thì try-catch sẽ không hoạt động đúng.

try {
    db.User.get(userId, function(err, user) {
        if(err) {
            throw err
        }
        // ...
        usernameSlug = slugifyUsername(user.username)
        // ...
    })
} catch(e) {
    console.log(‘Oh no!)
}

Như trong trường hợp này, khi callback b.User.get(...) có lỗi mà bạn lại throw lỗi thì các Exception của các hàm tiếp theo sẽ không thể catch được nữa.

#Sử dụng Console.log một cách bừa bãi

Trong Node.js, Console.log cho phép bạn in tất cả mọi thứ ra màn hình console. Không giống như Java, bạn chỉ có thể truyền vào một String. Với Console.log, bạn có thể truyền vào một Object, nó sẽ in ra dưới dạng một Object theo đúng nghĩa đen.

Chính vì Console.log tiện lợi như vậy nên nhiều bạn developer sử dụng console.log ở khắp mọi nơi. Tuy nhiên, bạn nên tránh việc đặt console.log ở tất cả mọi nơi để debug. Sau này không cần thì comment nó lại.

Thay vào đó, bạn nên sử dụng một thư viện hỗ trợ đặt log. Tùy thuộc vào hình thức bạn release sản phẩm như: release chính thức, hay debug… mà bật/tắt việc in các log một cách tự động thay vì phải đi xóa console.log thủ công.

Bài viết gốc được đăng tải tại vntalking.com

Xem thêm:

Xem thêm việc làm IT hấp dẫn trên TopDev