Học lập trình web bằng Nodejs cơ bản đến nâng cao

Căn bản của Node.js là lập trình sử dụng callback và non-blocking I/O. Căn bản của call back đó là truyền call back function như là tham số trong một hàm khác. Khi nào hàm khác xử lý xong, hoặc có sự kiện cần bắt thì sẽ gọi call back function. Có 2 loại callback:

  • Synchronous call back: call back thực thi luôn trong thread ban đầu mà ở đó callback được định nghĩa hoặc truyền vào. Synchronous call back có thể truy xuất để local variable trong stack của đoạn lệnh gọi nó (original stack).
  • Asynchronous call back: call back thực thi ở một thread khác, thường sẽ không truy xuất được local variable trong stack của original stack. Tuy nhiên có ngoại lệ closure cho phép asynchronous call back sử dụng được local variable của hàm bao ngoài hàm call back (ta gọi là outer function, còn call back thì tương đương với inner function)

Với những tác vụ kéo dài thời gian: lấy dữ liệu từ web service tại một server khác mất tầm trên 5 miliseconds, hoặc crawl toàn bộ thông tin web site, tải file ảnh, hay truy vấn đến cơ sở dữ liệu, nếu sử dụng synchronous call back rõ ràng là không ổn. Toàn bộ lệnh tiếp theo sẽ phải dừng để chờ kết quả trả về (wait hay block). Trường hợp này bắt buộc phải dùng asynchronous call back.

Zalgo là một lỗi căn bản trong lập trình Node.js, lần đầu tiên được Isaac Z. Schlueter, một trong những lập trình viên tham gia viết core của Node.js, viết ra trong blog post Designing APIs for Asynchrony . Lỗi Zalgo xảy ra khi lập trình viên trộn lẫn synchronous call back với asynchronous call back trong control flow : if then else hoặc loop. Việc này khiến cho ứng dụng Node.js cực kỳ khó dự đoán thứ tự thực thi code. Đôi khi main thread bị chặn lại (block) để chạy synchronous call back , đôi khi thì không bị block và asynchronous call back được trả về sau đó. Minh họa luôn nhé:

Có lỗi Zalgo

function getData(useCache, callback) {
    var fun = " Hello World";
    if (useCache) {
        callback('cached data' + fun); //gọi synchronous call back
    } else {
        setTimeout(function(){  //Sử dụng setTimeout để giả lập một tác vụ dài, không blocking
            callback('loaded data' + fun);
        }, 1000);

    }
}

console.log("Do task A");
getData(true, function(data){  //thử thay đổi tham số true bằng false!
    processData(data);
});
console.log("Do task C");

function processData(data) {
    console.log('processData', data);
}

Nếu getData(false, ....) thì kết quả: Do task A và Do task C chạy liền nhau.

Do task A
Do task C
processData loaded data Hello World

Nếu getData(true, ....) thì kết quả: Do task A, sau đó processData rồi mới đến Do task C. Thứ tự thực thi các tác vụ đã bị thay đổi.

Do task A
processData cached data Hello World
Do task C

Trong thực tế, Zalgo gây ra tình huống lộn xộn như sau: Giả sử trước khi truy vấn dữ liệu thì buộc phải kiểm tra quyền (check priviledge) trước. Truy vấn dữ liệu thì có lựa chọn sử dụng cache hay không. Nhưng chỉ vì lựa chọn này, đôi khi chúng ta lại truy vấn dữ liệu trước cả bước kiểm tra quyền truy cập. 

Sửa lỗi Zalgo bằng process.nextTick

function getData(useCache, callback) {
    var fun = " Hello World";
    if (useCache) {
        process.nextTick(function(){
            callback('cached data' + fun);
        });
    } else {
        setTimeout(function(){
            callback('loaded data' + fun);
        }, 1000);

    }
}

console.log("Do task A");
getData(true, function(data){
    processData(data);
});
console.log("Do task C");

function processData(data) {
    console.log('processData', data);
}

Nhìn kỹ ta thấy ở trong khối lệnh if (cache) then đã thay lệnh gọi callback('cache data') bằng process.nextTick.

process.nextTick(function(){
  callback('cached data' + fun);
});

process.nextTick là gì?

process.nextTick(functionX) sẽ đặt hàm functionX vào cuối của event queue mà Node.js sẽ xử lý. Nhờ có process.nextTick thay vì chúng ta phải gọi hàm synchronous callback luôn, chen ngang vào event queue của Node.js thì chúng ta lịch sự xếp nó vào cuối hàng đợi. Mọi thứ sẽ được thực thi tuần tự, dễ hiểu.

Lỗi Zalgo lấy tên một nhân vật trong truyện tranh, chuyên quấy thối, trêu trọc người khác. Vậy đừng để Zalgo xảy ra trong ứng dụng Node.js của bạn. Happy coding!