Trong JavaScript, callback là một hàm được truyền dưới dạng đối số cho một hàm khác và sẽ được thực thi khi hàm đó hoàn thành nhiệm vụ. Callback thường được sử dụng để đảm bảo rằng một số đoạn mã không chạy cho đến khi một nhiệm vụ cụ thể đã hoàn thành, nhất là trong lập trình bất đồng bộ (asynchronous programming).

Cách hoạt động của Callback

JavaScript là ngôn ngữ non-blockingsingle-threaded, điều đó có nghĩa là nó xử lý từng tác vụ một. Khi một tác vụ cần nhiều thời gian để hoàn thành (chẳng hạn như yêu cầu HTTP, truy vấn cơ sở dữ liệu, hoặc đọc tệp từ ổ cứng), JavaScript không chờ đợi tác vụ đó hoàn thành mà chuyển sang thực hiện các tác vụ khác. Đây là lúc callback trở nên hữu ích: callback được gọi khi nhiệm vụ dài hạn hoàn thành.

Ví dụ về Callback

Giả sử bạn có một hàm doTask và bạn muốn gọi một hàm khác, chẳng hạn notifyComplete, khi nhiệm vụ đó đã hoàn tất:

function doTask(callback) {
    console.log("Bắt đầu nhiệm vụ...");
    // Giả lập thời gian xử lý với setTimeout
    setTimeout(() => {
        console.log("Nhiệm vụ đã hoàn thành.");
        callback(); // Gọi hàm callback khi nhiệm vụ hoàn thành
    }, 2000);
}

function notifyComplete() {
    console.log("Thông báo: Nhiệm vụ đã hoàn thành!");
}

doTask(notifyComplete);

Trong ví dụ này:

  • doTask là hàm chính thực hiện nhiệm vụ và nhận một callback là notifyComplete.
  • Sau khi doTask hoàn thành nhiệm vụ (sau 2 giây trong trường hợp này), nó gọi hàm notifyComplete để thông báo rằng nhiệm vụ đã hoàn tất.

Callback trong hàm bất đồng bộ

Callback thường được sử dụng trong lập trình bất đồng bộ, nơi JavaScript thực hiện các tác vụ dài như gọi API hoặc xử lý dữ liệu từ cơ sở dữ liệu. Dưới đây là ví dụ về việc sử dụng callback trong một yêu cầu HTTP giả lập:

function makeRequest(url, callback) {
    console.log("Gửi yêu cầu đến " + url);
    setTimeout(() => {
        // Giả lập kết quả từ yêu cầu
        const response = "Dữ liệu từ " + url;
        callback(response); // Gọi callback và truyền kết quả
    }, 3000);
}

function processResponse(response) {
    console.log("Kết quả nhận được: " + response);
}

makeRequest("https://example.com", processResponse);

Trong ví dụ trên:

  • Hàm makeRequest giả lập một yêu cầu HTTP đến https://example.com và khi nhận được kết quả (sau 3 giây), nó gọi processResponse để xử lý kết quả.

Callback Hell

Khi bạn sử dụng nhiều callback lồng nhau, điều này có thể dẫn đến cái gọi là “callback hell”, khiến cho mã trở nên khó đọc và khó bảo trì:

doTask1(() => {
    doTask2(() => {
        doTask3(() => {
            doTask4(() => {
                console.log("Hoàn thành tất cả các nhiệm vụ");
            });
        });
    });
});

Để khắc phục vấn đề này, các khái niệm như PromiseAsync/Await được giới thiệu để làm cho mã dễ quản lý hơn.

Lợi ích của Callback

  1. Lập trình bất đồng bộ: Callback cho phép xử lý nhiều tác vụ mà không cần đợi từng tác vụ hoàn thành trước khi bắt đầu tác vụ khác.
  2. Tăng tính linh hoạt: Bạn có thể truyền các hàm khác nhau dưới dạng callback để tạo ra các hành vi khác nhau tùy thuộc vào ngữ cảnh.
  3. Tối ưu hóa hiệu suất: Thay vì chờ đợi các tác vụ dài hoàn thành, JavaScript có thể tiếp tục thực thi các tác vụ khác.

Kết luận

Callback là một khái niệm quan trọng trong JavaScript, đặc biệt khi làm việc với lập trình bất đồng bộ. Mặc dù callback rất mạnh mẽ, bạn nên cẩn thận với việc lồng quá nhiều callback để tránh tình trạng “callback hell.”