
Written on 27th June 2024 by Carter Phan.
Callback function có thể được hiểu nôm na như sau: callback tức là ta truyền một đoạn code (Hàm A) này vào một đoạn code khác (Hàm B). Tới một thời điểm nào đó, Hàm A sẽ được hàm B gọi lại (callback). Ví dụ đơn giản về callback function trong jQuery, trong ví dụ này thì phương thức click đã hỗ trợ bạn truyền một callback function.
$('#test').click(function(){
// đây là callback function
});
Điều này rất quan trọng bởi nếu bạn không kiểm tra giá trị mà người dùng truyền vào là một function thì bạn không thể thực thi được, đây là sự khác biệt giữa một lập trình viên non kinh nghiệm và nhiều kinh nghiệm. Xem ví dụ dưới đây để hiểu về cách kiểm tra:
function showPopup(callback) {
if (typeof callback !== 'function'){
alert('Bạn phải truyền vào là một function');
return false;
}
// do something
}
Hàm được xây dựng trong Object là hàm được định nghĩa thông qua key của object và giá trị của key là một hàm. Trong ví dụ này hàm setName được xây dựng bên trong object personInfo
var personInfo = {
name: 'Khoa',
setName : function (name) {
this.name = name;
}
}
Theo đúng nguyên tắc thì hàm callback là một hàm đơn phương nên khi bạn sử dụng từ khóa this trong hàm thì nó sẽ hiểu this lúc này chính là đối tượng Window Object, vì vậy cho dù bạn định nghĩa hàm callback nằm trong một object thì không thể truy cập đến dữ liệu của object thông qua từ khóa this.
Ở phần trên mình đã đưa ra lưu ý khi sử dụng this trong hàm callback thì this sẽ trỏ tới đối tượng window chứ không phải đối tượng chứa hàm callback, vậy có cách nào khắc phục tình trạng này không? Có đấy, chúng ta sẽ sử dụng phương thức apply của hàm callback. Cú pháp như sau:
// Trước đây
callback(var1, var2, ...);
// Bây giờ
callback.apply(callbackObject, [var1, var2, ... ]);
Dưới đây là đoạn code khắc phục lỗi ví dụ phía trên:
// Object chứa hàm callback
var personInfo = {
name : 'khoa',
setName : function(name){
// giá trị này sẽ không có tác dụng với key name trong object này
// nếu như ta sử dụng nó là một callback function
this.name = name;
}
};
// Hàm có tham số callback
function test(callback, callbackObject){
var name = "Nguyễn Đình Khoa";
callback.apply(callbackObject, [name]);
}
// Gọi đến hàm và truyền hàm callback vào
test(personInfo.setName, personInfo);
// Kết quả: Nguyễn Đình Khoa
document.write(personInfo.name);
Như ta đã biết, hàm callback được thực thi bên trong 1 hàm khác, nếu ta tiếp tục có hàm callback bên trong một callback khác thì thế nào? Vòng lặp vô tận “callback bên trong callback bên trong callback … ” sẽ có khả năng xảy ra. Thứ quái quỷ này được gọi là callback hell – địa ngục callback, ta sẽ rất hay gặp vấn đề này trong khi xử lí các lệnh bất đồng bộ, kiểu như:
p_client.open(function(err, p_client) {
p_client.dropDatabase(function(err, done) {
p_client.createCollection('test_custom_key', function(err, collection) {
collection.insert({'a':1}, function(err, docs) {
// ...
// và nhiều callback nữa
});
});
});
});
Khi callback hell xuất hiện, logic xử lí của chương trình sẽ trở nên cực kì phức tạp và khó nắm bắt, khi có lỗi xảy ra ta rất khó để debug cũng như giải quyết.