Callback function
là một khái niệm không còn mới mẻ nữa, có thể các bạn đã từng dùng nó nhưng các bạn lại không để ý thôi. Trong bài viết này mình sẽ nói về callback function trong JavaScript để các bạn có thể hiểu bản chất của vấn đề.

Callback function trong JavaScript là gì?
Để được xem là callback function
thì nó phải đáp ứng được 3 yếu tố sau:
Tất nhiên rồi, đầu tiên để được xem là
callback function
thì nó phải là một hàm.
Thứ 2
callback function
phải được truyền vào function khác như một đối số.
Thứ 3 c
allback function
phải được gọi ở hàm nhận đối số
Nói đến đây thì các bạn có thể vẫn hơi mông lung nhỉ, hãy xem qua một ví dụ mà các bạn hay sử dụng nhé:
1 2 3 |
setTimeout(function () { // Code }, 500); |
Hoặc
1 2 3 |
$('#demo').onclick(function () { // Code }) |
Ở 2 ví dụ trên toàn bộ là các hàm hay sử dụng trong quá trình làm việc. Và chúng là các callback function, không phải toàn bộ nhé, ý mình nói callback function
ở đây là hàm được truyền vào như một tham số của một hàm khác. Cụ thể ở đây là đoạn này:
1 2 3 4 |
// Đây mới là callback function function () { // Code } |
Chính xác đoạn này chính là callback function
. Các bạn hãy đối chiếu lại với điều kiện ở đầu bài xem chúng có phải là callback function
không nhé.
Chúng ta có thể viết riêng một hàm ở bên ngoài và truyền vào như thế này để bạn có thể dễ hình dung hơn.
1 2 3 4 5 6 7 8 9 10 |
// Tạo hàm A với một đối số function A(callbackDemo) { callbackDemo(); // Gọi hàm được truyền vào } // Là một callback function function B() { console.log('Đây là callback function'); } // Gọi hàm A và truyền hàm B vào như một đối số A(B); |
Cách một callback function hoạt động
Tên của nó cũng đã cung cấp cho chúng ta hiểu được phần nào tác dụng của nó rồi đúng không nào. JavaScript là một ngôn ngữ hướng sự kiện và bất đồng bộ, do đó tất cả các kiểu dữ liệu đều được xem là một Object và tất nhiên ta có thể truyền function vào như một đối số của hàm khác.
Hãy xem xét lại ví dụ ở trên nhé:
1 2 3 4 5 6 7 8 9 10 |
// Tạo hàm A với một đối số function A(callbackDemo) { callbackDemo(); // Gọi hàm được truyền vào } // Là một callback function function B() { console.log('Đây là callback function'); } // Gọi hàm A và truyền hàm B vào như một đối số A(B); |
Như các bạn đã biết một hàm sẽ chỉ thực thi khi chúng ta gọi nó. Mà muốn gọi hàm thì chúng ta cần gọi toán tử call()
và thực tế là gọi tên function và thêm cặp dấu ngoặc tròn ()
như thế này: B()
.
Do đó khi chúng ta truyền callback function
như một đối số của hàm khác, chúng ta không hề thêm dấu ()
nên nó sẽ không được thực thi. Cho đến khi ở trong hàm chính chúng ta gọi hàm callback ra thì chúng mới được thực thi.
1 2 3 4 |
// Tạo hàm A với một đối số function A(callbackDemo) { callbackDemo(); // Gọi hàm được truyền vào } |
Ở ví dụ trên thì callbackDemo
là một function, bạn có thể sử dụng typeof callbackDemo
để kiểm tra kiểu dữ liệu của nó. Và ở bên trong hàm nó đã được thêm dấu ()
nên đến lúc này thì nó mới được thực thi.
Tại sao phải cần callback function trong JavaScript
JavaScript sẽ thực thi code từ trên xuống dưới và từ trái sang phải. Đôi khi cũng có vài trường hợp chúng ta muốn thực thi một function sau khi một điều gì đó xảy ra và không theo thứ tự. Đây gọi là bất đồng bộ trong JavaScript.
1 2 3 4 5 6 7 8 |
function A() { console.log('Function A'); } function B() { console.log('Function B'); } A(); B(); |
Thông thường code sẽ chạy từ trên xuống dưới nên ở ví dụ trên, hàm A() sẽ thực thi trước sau đó đến hàm B() và kết quả sẽ như mong đợi:

Nhưng set ở một trường hợp khác, nếu trong hàm A() chúng ta có gọi một Api hoặc Ajax gì đó thì điều gì sẽ xảy ra. Để tái hiện trường hợp này tôi sẽ sử dụng hàm setTimeout() để trì hoãn hàm A() lại:

Như bạn thấy thì kết quả thì hoàn toàn đối nghịch với ví dụ một. Tại sao vậy ạ? Không phải JavaScript thực thi hàm B() trước mà là JavaScript vẫn sẽ thực hiện hàm B() trong khi đợi phản hồi từ hàm A(). Vậy bây giờ mình muốn hàm A thực thi xong thì hàm B mới thực thi, ta có thể áp dụng callback function vào trường hợp này.

Ta có thể xem các đoạn code Ajax thực ra thì chúng có gọi callback function bên trong:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Var someUlr = …; function successCallback(){ //success code } function completeCallback(){ //complete code } function errorCallback(){ //error code } $.ajax({ url: someUrl, success: successCallback, complete: completeCallback, error: errorCallback }) |
Lưu ý khi sử dụng callback function
1. Tham số truyền vào phải là một function
Nguyên tắc của callback function trong JavaScript chính là phải được gọi lại. Do đó nếu truyền một kiểu dữ liệu khác như string hoặc number thì sẽ gặp lỗi ngay. Do đó các bạn nên kiểm tra kiểu dữ liệu của đối số truyền vào bằng typeof trước khi gọi hàm callback:

Ở ví dụ trên tương đương với chúng ta gọi hàm 29() nhưng hoàn toàn không có hàm nào với tên là 29 cả. Để khắc phục điều này thì chúng ta có thể kiểm tra kiểu dữ liệu của đối số truyền vào:

2. Cẩn thận với this khi hàm callback nằm trong object
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
1 2 3 4 5 6 |
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.
Bạn hãy xem đoạn code sử dụng hàm setName
là một callback function dưới đây để hiểu rõ hơn:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// 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){ callback('Nguyễn Đình Khoa'); } // Gọi đến hàm và truyền hàm callback vào test(personInfo.setName); // Vẫn kết quả cũ Khoa, tức là hàm callback setName đã ko tác động // gì tới thuộc tính name document.write(personInfo.name); // Xuống hàng document.write('<br/>'); // kết quả Nguyễn Đình Khoa, tức đối tượng window đã tự tạo ra một key name // và giá trị của nó chính là giá trị ta đã sét trong hàm setName // => this chính là window object document.write(window.name); |
3. Khắc phục this khi hàm callback nằm trong object
Ở 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:
1 2 3 4 5 |
// 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 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); |
4. Callback Hell
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ư:
1 2 3 4 5 6 7 8 9 10 |
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.
Bên cạnh đó, callback hell cũng làm cho tính thẩm mĩ của code giảm đi đáng kể, khó đọc, khó maintain.
Các bạn có thể tìm hiểu thêm về promise và async/await để giải quyết vấn đề callback hell này nhé!
Kết luận
Vừa rồi chúng ta đã cùng tìm hiểu về callback function trong JavaScript. Ở bài tiếp theo sẽ tìm hiểu về Arrow function nhé.
Tham khảo: