Chắc hẳn những bạn nào quen lập trình Nodejs hay Javascript rồi thì khái niệm Callback không còn xa lạ nữa. Nhưng với người mới như mình thì callback hell trong javascript luôn là một ám ảnh. Vậy Callback hell là gì? Nó có hay xảy ra khi làm việc với Nodejs không?

Mình phải thừa nhận một điều là mình quyết định hành động học Nodejs chẳng qua bị sếp ép mà thôi. Với xuất phát điểm từ lập trình Java, cho đến lập trình Android nên tư duy giải quyết và xử lý bất đồng bộ của Javascript thực sự làm mình bồn chồn .
Như mọi người cũng biết, việc giải quyết và xử lý những tác vụ trong Javascript là bất đồng bộ. Tức là những tác vụ sẽ được Javascript đẩy hết một sự kiện loop .

Các bạn có thể xem video bên dưới để hiểu rõ hơn về Event Loop trong Javascript nhé.

Tác vụ nào hoàn thành xong thì sẽ được bắn sự kiện để thông tin và trả tác dụng. Do đó những tác vụ sẽ không được triển khai theo đúng trình tự như tất cả chúng ta nhìn trong code .
Từ đó, tất cả chúng ta sử dụng callback để hoàn toàn có thể tinh chỉnh và điều khiển việc thực thi những tác vụ theo đúng trình tự mong ước .

Tuy nhiên, nếu lạm dụng callback mà không được thiết kế cẩn thận sẽ làm cho code của bạn trở lên khó đọc, khó bảo trì.
Callback hell là gì? 6 cách "trị" callback hell trong javascript dễ nhất

# Callback hell trong javascript là gì ?

Chắc hẳn bạn đang rất muốn biết thực chất callbackhell trong javascript là gì đúng không ?
Thực ra callback hell trong javascript chỉ là bạn thực thi quá nhiều callback lồng nhau. Đại khái, callback hell sẽ có hình dạng như bên dưới .

getData(function(a){  
    getMoreData(a, function(b){
        getMoreData(b, function(c){ 
            getMoreData(c, function(d){ 
                getMoreData(d, function(e){ 
                    ...
                });
            });
        });
    });
});

Nhìn qua đoạn code bạn có thấy khiếp không ?
Những người mới khởi đầu học Nodejs thường rất dễ bị lỗi này. Đơn giản vì những bạn chưa có một tư duy phong cách thiết kế chuẩn cho kiểu mạng lưới hệ thống hướng sự kiện .
Bài viết này, mình sẽ san sẻ 5 cách để những bạn hạn chế bị callback hell trong javascript mà dễ triển khai nhất .

>> Đọc ngay: Định nghĩa hàm Javascript – 3 cách cơ bản nhất

# 6 cách giải quyết và xử lý callback hell trong javascript dễ nhất

1. Thiết kế ứng dụng theo dạng module

Cũng giống với những ngôn từ lập trình khác, một trong những cách để hạn chế sự phức tạp của code là module hóa .
Bất cứ khi nào bạn viết code, đừng cắm cổ vào viết ngay mà hãy dành một chút ít thời hạn để tâm lý xem mình viết như này đã tốt nhất chưa .
Bạn đang viết một đoạn code và đoạn code này Open ở rất nhiều nơi ? Hay những phần của đoạn code đó lại đang có vẻ như tái sử dụng được … Lúc này bạn hãy mạnh dạn nghĩ tới module hóa nó .
Bạn nên nhớ rằng, Nodejs được như ngày ngày hôm nay là do được thiết kế xây dựng trên hàng trăm ngàn modules khác nhau. Nodejs sẽ không là gì cả nếu không có những module. Nên việc bạn module mã nguồn của mình là đi đúng hướng với triết lý của Nodejs đấy .
Ví dụ cách viết một module. Bạn tạo một module tên là Test .

//node_modules/test/index.js
module.exports = {
  hello: function(name) {
    console.log("Hello, " + name);
  },
  bye: function(name) {
    console.log("Goodbye, " + name);
  }
};

Sau đó gọi ở ứng dụng như sau :

var greeter = require('test');

greeter.hello("Monkey");
greeter.bye("Steven");

2. Nên đặt tên cho callback trong javascript

Bạn hay bắt gặp cách viết callback là các hàm anonymous function. Tức là các hàm không có tên.

Ví dụ một đoạn code sử dụng callback là anonymous function. Và có đến 2 callback lồng nhau .

var fs = require('fs');

var myFile = '/tmp/test';  
fs.readFile(myFile, 'utf8', function(err, txt) {  
    if (err) return console.log(err);

    txt = txt + '\nAppended something!';
    fs.writeFile(myFile, txt, function(err) {
        if(err) return console.log(err);
        console.log('Appended text!');
    });
});

Nhìn vào đoạn code này sẽ khiến bạn mất vài giây để xem callback thực thi điều gì và được gọi từ đâu .
Để khắc phục điều này, đơn thuần bạn thêm một thao tác nhỏ là đặt tên cho callback. Nó sẽ giúp bạn dễ đọc code hơn, đặc biệt quan trọng khi những callback lồng nhau nhiều hơn .

var fs = require('fs');

var myFile = '/tmp/test';  
fs.readFile(myFile, 'utf8', function appendText(err, txt) {  
    if (err) return console.log(err);

    txt = txt + '\nAppended something!';
    fs.writeFile(myFile, txt, function notifyUser(err) {
        if(err) return console.log(err);
        console.log('Appended text!');
    });
});

Lúc này, bạn chỉ cần lướt qua là biết callback tiên phong thực thi việc nối những text lại với nhau. Còn callback thứ 2 là để thông tin cho người người dùng. Việc này giúp bạn tránh được callback hell trong javascript thuận tiện đúng không ?

>> Có ích cho bạn: Tự xây dựng ứng dụng web với ExpressJS

3. Định nghĩa hàm trước khi gọi để tránh callback hell trong javascript

Vẫn với ví dụ ở trên, việc bạn đặt tên đã giúp cho code dễ đọc hơn rất nhiều. Nhưng nó vẫn còn khá cồng kềnh.

Bạn triển khai thêm một bước nữa, đó là tách riêng và định nghĩa những callback riêng ra. Hãy cứ tách hàm khi hoàn toàn có thể !

var fs = require('fs');

function notifyUser(err) {  
    if(err) return console.log(err);
    console.log('Appended text!');
};

function appendText(err, txt) {  
    if (err) return console.log(err);

    txt = txt + '\nAppended something!';
    fs.writeFile(myFile, txt, notifyUser);
}

var myFile = '/tmp/test';  
fs.readFile(myFile, 'utf8', appendText);  

Bạn thế code trên đẹp trai hơn chưa ? 🙂
Mặc dù cách viết code đã xử lý được phần nào yếu tố. Nhưng nó vẫn chưa phải là giải pháp tốt nhất. Nếu bạn đọc lại code mà không nhớ đúng chuẩn hàm đó làm gì, bạn sẽ phải trace lại code, mà thường thì code của hàm đó lại trôi tuột ở đâu đó. Rất mất thời hạn .
Chúng ta còn có giải pháp tốt hơn, ngay phía bên dưới thôi !

4. Sử dụng module Async. js

Đúng với tên gọi của nó, module Async. js sẽ giúp bạn giải quyết và xử lý những hàm bất độ theo cách đồng nhất .
Module này có rất nhiều methods để bạn chọn như series, parallel, waterfall … Vì vậy, bạn nên dành chút thời hạn để đọc tài liệu hướng dẫn của tác giả trước khi quyết định hành động chọn method nào .
Async. js thực sự là một thư viện tốt, nhưng nếu lạm dụng quá thì cũng không tốt. Bạn nên nhớ Nodejs là nền tảng được phong cách thiết kế cho mạng lưới hệ thống giải quyết và xử lý bất đồng bộ, với ưu điểm giải quyết và xử lý realtime. Nên nếu dự án Bất Động Sản toàn sử dụng Async. js để giải quyết và xử lý những tác vụ theo kiểu đồng nhất tuần tự là tự đập bỏ điểm mạnh của Nodejs .
Đây là đoạn code sử dụng Async. js cho ví dụ trên :

var fs = require('fs');  
var async = require('async');

var myFile = '/tmp/test';

async.waterfall([  
    function(callback) {
        fs.readFile(myFile, 'utf8', callback);
    },
    function(txt, callback) {
        txt = txt + '\nAppended something!';
        fs.writeFile(myFile, txt, callback);
    }
], function (err, result) {
    if(err) return console.log(err);
    console.log('Appended text!');
});

Nhìn cũng khá tường minh và dễ hiểu phải không ?

5. Sử dụng Promises

Mặc dù khái niệm Promies hơi khó hiểu chút khi mới tiếp cận. Nhưng theo mình thì đây là một khái niệm quan trọng mà bạn nên cố hiểu khi học Javascrip / Nodejs .
Promises giúp làm giảm số dòng code đáng kể, nó còn giúp mã dễ đọc, dễ bảo dưỡng hơn nhiều .
Quay lại ví dụ bắt đầu, nếu sử dụng Promises sẽ như sau :

var Promise = require('bluebird');  
var fs = require('fs');  
Promise.promisifyAll(fs);

var myFile = '/tmp/test';  
fs.readFileAsync(myFile, 'utf8').then(function(txt) {  
    txt = txt + '\nAppended something!';
    fs.writeFile(myFile, txt);
}).then(function() {
    console.log('Appended text!');
}).catch(function(err) {
    console.log(err);
});

Nói về Promises thì còn nhiều điều để nói lắm. Ở bài viết này mình sẽ không trình diễn sâu về nó ( mặc dầu rất thích nói về Promises ). Hẹn những bạn ở bài viết sau nhé !

6. Async / Await nhằm mục đích giảm năng lực xảy ra callback hell trong javascript

Kể từ phiên bản ES7, Javascript có một khái niệm mới là Async / Await. Khi sử dụng hàm async, code của bạn sẽ trông giống như đồng nhất như thực ra là bất đồng bộ. Thế mới hay !
Ví dụ đoạn code sau :

async function getUser(id) {  
    if (id) {
        return await db.user.byId(id);
    } else {
        throw 'Invalid ID!';
    }
}

try {  
    let user = await getUser(123);
} catch(err) {
    console.error(err);
}

Ở đoạn code trên, hàm db.user.byId(id) sẽ trả về một Promises, và lẽ ra khi hàm này được sử dụng thì kết quả sẽ trả về trong hàm .then().

Tuy nhiên, với từ khóa await, bạn sẽ lấy trực tiếp tác dụng trả về .

Lưu ý: await chỉ được sử dụng với hàm được khái báo với từ khóa async.

# Tạm kết

Như vậy, qua bài viết này bạn đã biết callback hell trong javascript là gì rồi đúng không ? Khi viết code thì những sai sót phổ cập như callback hell trong javascript là khó tránh khỏi. Tuy nhiên, hãy hạn chế nó càng nhiều càng tốt .
Chỉ cần bạn nỗ lực viết code chậm lại chút, tâm lý một chút ít trước khi viết code. Javascript là một ngôn n gữ “ dễ dãi ”, đây cũng vừa là ưu điểm và điểm yếu kém của Javascript .

Hãy là người viết code thông thái!

Xem thêm: 40n60 là gì

Bạn hoàn toàn có thể tìm hiểu thêm thêm những bài viết hay ho về Nodejs khác trên VNTALKING :
Mình xin kết thúc bài viết tại đây, nếu bạn có khúc mắc gì thì đừng ngại mà để lại phản hồi bên dưới nhé .

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *