Cách giải quyết callbacks lồng nhau hay Callback Hell trong Javascript

JavaScript là một trong những ngôn ngữ được sử dụng phổ biến nhất. Tuy nhiên đôi khi bạn gặp phải tình trạng có quá nhiều đoạn Callback lồng nhau, đặc biệt là trong việc xử lý các hàm JavaScript bất đồng bộ chẳng hạn như sau:

firstFunction(args, function() {
  secondFunction(args, function() {
    thirdFunction(args, function() {
      // And so on…
    });
  });
});

Tuy nhiên, nếu lạm dụng quá nhiều các hàm callback mà không có phương pháp code đúng đắn sẽ dẫn đến tình trạng code của chúng ta cực kì phức tạp, cực kì khó đọc. Vì vậy trong bài viết này Suntech sẽ giới thiệu các bạn cách giải quyết các Callbacks lồng nhau và tránh Callback Hell.

Xây dựng một Callback hell

Trước khi đi sâu vào các giải pháp thì đầu tiên chúng ta cùng nhau xây dựng một Callback hell minh họa để dễ hình dung. Nó sẽ giống như thế này:

const makeBurger = nextStep => {
  getBeef(function(beef) {
    cookBeef(beef, function(cookedBeef) {
      getBuns(function(buns) {
        putBeefBetweenBuns(buns, beef, function(burger) {
          nextStep(burger);
        });
      });
    });
  });
};

Trông khá phức tạp và khó đọc phải không nào? Bây giờ chúng ta sẽ đi giải quyết nó bằng 4 phương pháp.

1. Viết comment

// Makes a burger
// makeBurger contains four steps:
//   1. Get beef
//   2. Cook the beef
//   3. Get buns for the burger
//   4. Put the cooked beef between the buns
//   5. Serve the burger (from the callback)
// We use callbacks here because each step is asynchronous.
//   We have to wait for the helper to complete the one step
//   before we can start the next step

const makeBurger = nextStep => {
  getBeef(function(beef) {
    cookBeef(beef, function(cookedBeef) {
      getBuns(function(buns) {
        putBeefBetweenBuns(buns, beef, function(burger) {
          nextStep(burger);
        });
      });
    });
  });
};

Cách đơn giản nhất có lẽ là viết comment đoạn code chứa Callback hell. Những ghi chú như trên sẽ giúp đoạn code của bạn dễ hiểu hơn và thay vì nghĩ: "WTF" mỗi khi thấy Callback hell thì bây giờ bạn sẽ hiểu tại sao nó lại được viết như vậy.

2. Chia các Callback thành các chức năng khác nhau

Chúng ta cùng nhau chuyển Callback đầu tiên ( Function getBeef ) thành

const getBeef = nextStep => {
  const fridge = leftFright;
  const beef = getBeefFromFridge(fridge);
  nextStep(beef);
};

và Callback thứ 2 thành

const cookBeef = (beef, nextStep) => {
  const workInProgress = putBeefinOven(beef);
  setTimeout(function() {
    nextStep(workInProgress);
  }, 1000 * 60 * 20);
};

Tương tự chúng ta cũng có thể tách các Callback 3, 4 thành các function nhỏ hơn sở hữu các chức năng khác nhau. Đây là một cách khá hay để phòng tránh Callback hell phải không nào.

3. Sử dụng Promises

Phương pháp này không loại bỏ việc sử dụng các lệnh Callback mà Promises sẽ làm các Callback hell dễ dàng quản lí hơn rất nhiều. Thay vì gặp phải các Callback như ở trên thì bằng việc sử dụng Promises bạn sẽ có

const makeBurger = () => {
  return getBeef()
    .then(beef => cookBeef(beef))
    .then(cookedBeef => getBuns(beef))
    .then(bunsAndBeef => putBeefBetweenBuns(bunsAndBeef));
};

// Make and serve burger
makeBurger().then(burger => serve(burger));

Để đạt được kiểu đơn giản này, tất cả các hàm được sử dụng trong ví dụ sẽ phải được Promisised. Nếu bạn băn khoăn về Promises thì bạn có thể tham khảo bài viết tại đây.

4. Sử dụng các hàm không đồng bộ (Async)

Việc sử dụng các hàm không đồng bộ sẽ giúp code của bạn sạch hơn (Clean code) và dễ bảo trì hơn rất nhiều. Để sử dụng được hàm không đồng bộ thì bạn cần:

  • Biết cách chuyển đổi từ Callback sang Promises
  • Sử dụng các hàm không đồng bộ

Với các hàm không đồng bộ, bạn có thể viết makeBurgernhư thể nó lại đồng bộ!

const makeBurger = async () => {
  const beef = await getBeef();
  const cookedBeef = await cookBeef(beef);
  const buns = await getBuns();
  const burger = await putBeefBetweenBuns(cookedBeef, buns);
  return burger;
};

// Make and serve burger
makeBurger().then(serve);

Có một cải tiến có thể thực hiện ở đây. Chúng ta có thể awaitcả hai với Promise.all.

const makeBurger = async () => {
  const [beef, buns] = await Promise.all(getBeef, getBuns);
  const cookedBeef = await cookBeef(beef);
  const burger = await putBeefBetweenBuns(cookedBeef, buns);
  return burger;
};

// Make and serve burger
makeBurger().then(serve);

Tổng kết

Vậy là trong bài viết này mình đã giới thiệu cho các bạn 4 cách để xử lí Callbacks hell. Hi vọng các giải pháp trên sẽ giúp ích cho các bạn!

Trần Quang Hào
SUNTECH VIỆT NAM   Đăng ký để nhận thông báo mới nhất