Bài trước: Web back-end (9) - Một số chủ đề JavaScript (2)
-----
10. Hàm callback
10.1 Hàm callback là gì
Trong JavaScript, hàm callback là một hàm được truyền vào một hàm khác như một tham số, và sẽ được thực thi sau khi hàm "cha" hoàn thành một tác vụ nào đó. Nói một cách đơn giản, nó là một cách để đảm bảo một đoạn mã được thực thi sau khi một đoạn mã khác hoàn thành.
Hàm callback cũng là một trong ba kỹ thuật được sử dụng để lập trình bất đồng bộ trong JavaScript.
Callback nghĩa là gọi lại.
Các đặc điểm của hàm callback
- Truyền như tham số: callback là một hàm được truyền vào hàm khác để thực thi sau khi một điều kiện hoặc tác vụ hoàn tất.
- Thực thi bất đồng bộ: thường dùng để xử lý các tác vụ không chặn luồng chính (non-blocking), như truy cập thiết bị (đọc/ghi tập tin), gọi API, hoặc trong các hàm hẹn giờ (timer).
- Tính linh hoạt: có thể là hàm ẩn danh (anonymous function), hàm mũi tên (arrow function), hoặc hàm đã được định nghĩa trước (function expression).
- "Callback Hell": nếu lồng quá nhiều callback, mã nguồn có thể trở nên khó đọc và bảo trì. Callback hell thường được giải quyết bằng Promise hoặc async/await.
Một số tính huống có sử dụng hàm callback
- Xử lý sự kiện (event handling): gắn hàm callback để phản hồi hành động của người dùng (ví dụ: click, hover, submit).
- Tác vụ bất đồng bộ: gọi API, đọc/ghi tập tin, hoặc sử dụng trong các hàm hẹn giờ (ví dụ: setTimeout, setInterval).
- Xử lý mảng: sử dụng trong các phương thức như forEach, map, filter, reduce.
- Hoàn thành tác vụ: đảm bảo một đoạn mã chạy sau khi tác vụ khác hoàn tất (ví dụ: tải dữ liệu từ server rồi hiển thị lên giao diện).
- Tùy chỉnh logic: cho phép người dùng truyền logic riêng vào hàm tổng quát.
Một số ví dụ sử dụng hàm callback
Ví dụ 1. Dùng hàm callback trong xử lý sự kiện.
Khi người dùng nhấp vào một nút “Đăng ký”, một sự kiện "click" được kích hoạt. Bạn có thể sử dụng hàm callback để xử lý sự kiện này và thực hiện một hành động nào đó.
[callback1.html]
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<button id="dang-ky">Đăng
ký</button>
<script>
document.getElementById("dang-ky").addEventListener("click",
() => {
alert("Nút đã được bấm!");
});
</script>
</body>
</html>
Mở tập tin callback1.html bằng trình duyệt để xem kết quả.
Ví dụ 2. Sử dụng hàm callback trong các hàm hẹn giờ (ví dụ: setTimeout, setInterval).
Khi hàm setTimeout() chạy, nó sẽ đưa luồng xử lý vào trạng thái chờ với thời gian được xác định ở tham số thứ 2 (ví dụ chờ 5000ms = 5 giây). Sau khi hết thời gian chờ, hàm callback sẽ được gọi và thực thi.
setTimeout(()
=> {
console.log("Đã hết 5 giây! (sau 5 giây)");
},
5000);
console.log("Đang chờ");
Kết quả chạy:
Đang chờ
Đã hết 5 giây! (sau 5 giây)
Ví dụ 3. Sử dụng callback trong các phương thức như forEach, map, filter, reduce.
const
numbers = [1, 2, 3, 4];
numbers.forEach(num
=> console.log(num * num));
// Kết quả: 1, 4, 9, 16
Ví dụ 4. Dùng callback trong gọi API
function
getUser(userId, callback) {
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then(response => response.json())
.then(data => callback(null, data)) // Thành công, gọi callback với dữ liệu
.catch(error => callback(error, null)); // Lỗi, gọi callback với lỗi
}
getUser(1, function(error, user) {
if (error) {
console.error("Lỗi:", error);
}
else {
console.log("Tên người dùng:", user.name);
}
});
//
Kết quả: "Tên người dùng: Leanne Graham"
Ví dụ 5. Callback tùy chỉnh logic, cho phép người dùng truyền logic riêng vào hàm tổng quát. Hàm callback x => x * x cho phép người dùng tự định nghĩa cách xử lý từng phần tử.
function
xuLyMang(arr, callback) {
for (let i = 0; i < arr.length; i++) {
arr[i] = callback(arr[i]);
}
return arr;
}
const numbers = [1, 2, 3];
const binhPhuong = xuLyMang(numbers, x => x * x);
console.log(binhPhuong);
// Kết quả: [1, 4, 9]
const nhan2 = xuLyMang(numbers, x => x * 2);
console.log(nhan2);
// Kết quả: [2, 8, 18]
Ví dụ 6. “Callback hell”, hiện tượng lồng quá nhiều callback dẫn đến mã nguồn khó đọc.
setTimeout(() => { console.log("Bước 1 hoàn tất");
setTimeout(() => { console.log("Bước 2 hoàn tất");
setTimeout(() => { console.log("Bước 3 hoàn tất");
}, 1000);
},1000);
}, 1000);
Nhược điểm của hàm callback
- Callback Hell: lồng quá nhiều callback dẫn đến mã khó đọc.
- Xử lý lỗi phức tạp: cần kiểm tra lỗi thủ công trong mỗi callback.
- Không trực quan: với các luồng phức tạp, khó theo dõi thứ tự thực thi.
Vì vậy, bạn nên sử dụng Promise và Async/Await thay thế cho kỹ thuật callback.
10.2 Callback trong index.js
Quay trở lại ứng dụng web TeoShop.
Quan sát tập tin mã nguồn của index.js.
[index.js]
'use strict'
const express = require('express')
const app = express();
const port = process.env.PORT || 9000
// xu ly khi nguoi dung gui request toi web server
app.get("/", (req, res) => { res.send('Chao ban den voi TeoShop!');
});
// khoi dong web server
app.listen(port, () => { console.log(`server dang chay tren cong ${port}`);
});
Ở đoạn mã trên có 2 hàm chạy theo kiểu bất đồng bộ và sử dụng callback để cài đặt là: app.get() và app.listen().
Hàm app.get()
// xu ly khi nguoi dung gui request toi web server
app.get("/", (req, res) => { res.send('Chao ban den voi TeoShop!');
});
Hàm app.listen()
// khoi dong web server
app.listen(port, () => { console.log(`server dang chay tren cong ${port}`);
});
Mặc dù theo thứ tự trong mã nguồn, hàm app.get() nằm phía trước hàm app.listen(). Nhưng khi chạy ứng dụng (chạy tập tin index.js), hàm app.listen() được xử lý xong trước, nên có dòng thông báo “server dang chay tren cong 9000”. Trong khi, hàm app.get() đang chờ người dùng gửi request lên từ trình duyệt nên sẽ hoàn thành sau hàm app.listen(). Bạn chạy ứng dụng web trên máy, quan sát, để kiểm tra tình huống này.
Giải thích toàn bộ đoạn mã trong tập tin index.js
'use strict'
- Kích hoạt "strict mode" trong JavaScript. Strict mode giúp bạn viết mã an toàn hơn bằng cách đưa ra các lỗi khi bạn sử dụng các tính năng không an toàn hoặc các thực hành không tốt. Ví dụ: Strict mode sẽ ngăn bạn sử dụng các biến chưa được khai báo.
const express = require('express')
- Triệu gọi (thư viện) module express để sử dụng trong đoạn mã của bạn. Express là một framework web phổ biến cho Node.js, giúp bạn xây dựng các ứng dụng web một cách dễ dàng.
const app = express();
- Tạo một đối tượng (instance, thể hiện) của ứng dụng Express và gán nó cho biến app. Biến
app sẽ được sử dụng để định nghĩa các route và xử lý các request.
const port = process.env.PORT || 9000
- Định nghĩa cổng mà ứng dụng sẽ chạy trên đó. process.env.PORT là một biến môi trường có thể được thiết lập bởi môi trường triển khai (ví dụ: Heroku, AWS). || 9000 có nghĩa là nếu process.env.PORT không được thiết lập, cổng 9000 sẽ được sử dụng.
app.get("/", (req, res) => { res.send('Chao ban den voi ctk!');
});
- Định nghĩa một route cho request GET đến đường dẫn "/".
+ Khi người dùng truy cập đường dẫn "/", hàm callback (req, res) => { ... } sẽ được thực thi.
+ req là đối tượng request, chứa thông tin về request từ người dùng.
+ res là đối tượng response, được sử dụng để gửi phản hồi về cho người dùng.
+ res.send('Chao ban den voi ctk!'); gửi một chuỗi "Chao ban den voi ctk!" về cho người dùng.
app.listen(port, () => { console.log(`server dang chay tren cong
${port}`);
});
- Khởi động web server và lắng nghe các request đến cổng được chỉ định.
+ Khi server khởi động thành công, hàm callback ()=> { ... } sẽ được thực thi.
+ console.log(`server dang chay tren cong ${port}`);` in ra một thông báo cho biết server đang chạy trên cổng nào.
Tóm lại: đoạn mã trên tạo một web server đơn giản sử dụng Express. Khi người dùng truy cập đường dẫn "/" trên trình duyệt, server sẽ trả về chuỗi "Chao ban den voi ctk!".
10.3 Bài tập
Bài tập 10.1 Cài đặt các ví dụ trong bài học.
Câu 10.2 Hàm callback trong JavaScript là gì? Phát biểu nào sau đây không đúng?
A. Hàm callback là một hàm được truyền vào một hàm khác như một tham số.
B. Hàm callback thường được sử dụng để xử lý các tác vụ bất đồng bộ.
C. Hàm callback có thể là hàm ẩn danh, hàm mũi tên hoặc hàm đã được định nghĩa trước.
D. Hàm callback được thực thi ngay lập tức sau khi hàm "cha" bắt đầu thực hiện.
Câu 10.3 Callback hell trong JavaScript là gì? Phát biểu nào sau đây không đúng?
A. Callback hell xảy ra khi có quá nhiều hàm callback lồng nhau, khiến mã nguồn khó đọc và bảo trì.
B. Callback hell thường xuất hiện khi xử lý các tác vụ bất đồng bộ phức tạp.
C. Callback hell là một cách hiệu quả để quản lý các tác vụ bất đồng bộ trong JavaScript.
D. Callback hell có thể được giải quyết bằng Promise hoặc async/await.
-----
Cập nhật: 10/3/2025
Bài sau: Web back-end (11) - Lập trình giao diện