1. Nguyên tắc khi sử dụng Callbacks Function

1.1. Tham số truyền vào

Ta phải chắc chắn tham số truyền vào là một function . Điều này rất quan trọng bởi nếu ta không kiểm tra giá trị mà người dùng truyền vào là một function thì không thể thực thi được. Đây có thể coi 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.

Ví dụ:

function createPassword(callback) {
    if (typeof callback !== 'function'){
        alert('Bạn phải truyền vào là một function');
        return false;
    }
}

Ta thấy để kiểm tra một biến có phải là function hay không thì ta sử dụng typeof , nếu typeof có giá trị là “function” thì đó là một function .

1.2. Sử dụng this khi Callbacks nằm trong Object

Ta phải 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. Ví dụ trước cho mọi người dễ hình dung nhé. 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ù đị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 .

Đoạn code sau sử dụng hàm setName là một callback function :

// Object chứa hàm callback
var domainInfo = {
    name : 'laptrinhtudau.com',
    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('Thành Nguyễn');
}
 
// Gọi đến hàm và truyền hàm callback vào
test(domainInfo.setName);
 
// Vẫn kết quả cũ laptrinhtudau.com, tức là hàm callback setName đã ko tác động
// gì tới thuộc tính name
document.write(domainInfo.name); 
 
// Xuống hàng
document.write('<br/>');
 
// kết quả Thành Nguyễn, 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);

Cách khắc phục tình trạng này là 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 domainInfo = {
    name : 'laptrinhtudau.com',
    setName : function(name){
        this.name = name;
    }
};
 
// Hàm có tham số callback
function test(callback, callbackObject){
    var name = "Thành Nguyễn";
    callback.apply(callbackObject, [name]);
}
 
// Gọi đến hàm và truyền hàm callback vào
test(domainInfo.setName, domainInfo);
 
// Kết quả: Thành Nguyễn
document.write(domainInfo.name);

1.3. Callbacks trong Callbacks

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. 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 đó 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.

2. Tạo một Callbacks Function trong JavaScript

2.1. Tạo hàm callback trong JavaScript bằng từ khoá function

Chúng ta sử dụng từ khoá function để tạo hàm trong Js. Ta sẽ tạo một hàm, đặt tên và gọi nó tại nhiều thời điểm trong chương trình. Ví dụ tạo một hàm in tên hàng cộng giá tiền, và gọi nó trực tiếp như sau:

function check_price(name,price){
  document.write('Giá', name,':', price );
}

check_price('tomato', 120)//> Giá tomato : 120

Nếu chúng ta không gọi hàm trên trực tiếp mà sử dụng nó như là một đối số trong một hàm khác, thì khi đó nó sẽ trở thành một callback function trong Js.

Ví dụ truyền callback function này dưới dạng đối số trong một hàm khác như sau:

//Tạo hàm callback
function check_price(name,price){
  console.log('Giá ', name,':', price );
}

//Truyền hàm callback trên dưới dạng đối số vào một hàm higher-order function
function tomato(check_price){
  const name = "cà chua";
  const price = 100;
  check_price(name, price);
}

//Chạy hàm higher-order function để gọi lại hàm callback
tomato( check_price);
//> Giá  cà chua : 100

Sau khi chúng ta gọi hàm tomato() thì hàm tomato() sẽ gọi lại hàm check_price() . Thực thi hàm check_price() bên trong hàm tomato() . Khi đã có callbacks ta có thể sử dụng nó nhiều lần trong các hàm khác nhau.

function orange(orgin_price,tax,check_price){
  const name = "cam";
  let price = orgin_price * (1+ tax/100)
  check_price(name, price);
}

orange(500,20, check_price);
//> Giá  cam : 600

2.2. Tạo hàm callback trong JavaScript bằng hàm ẩn danh

Ta cũng có thể khai báo hàm callback trong Js bằng một hàm ẩn danh (Anonymous Function). Với hàm ẩn danh thì do nó không có tên, nên để có thể gọi nó nhiều lần trong các hàm khác nhau thì chúng ta cần phải gán nó vào một biến như sau:

//Tạo hàm callback và gán vào một biến
let helloworld = function (){
  alert("Laptrinhtudau");
}

Sau đó, ta có thể sử dụng nó như một callbacks . Ví dụ:


<button onclick="laptrinhtudau()">Click me</button>

Do tính chất của hàm ẩn danh có thể được viết trực tiếp dưới dạng một đối số của hàm khác, nên chúng ta cũng có thể viết trực tiếp một hàm callback như là đối số khi gọi hàm higher-order

//Tạo hàm higher-order function
function orange(orgin_price,tax,func){
  const name = "cam";
  func(name, orgin_price * (1+ tax/100));
}

//Chạy hàm higher-order function và ghi trực tiếp hàm callback trong đối số
orange(500,20, function (name,price){
  console.log('Giá ', name,':', price );
});
//> Giá  cam : 600

Ngoài cách viết trên, chúng ta cũng có thể sử dụng hàm mũi tên arrow để viết gọn hàm ẩn danh như sau:

function orange(orgin_price,tax,func){
  const name = "cam";
  func(name, orgin_price * (1+ tax/100));
}
orange(500,20,  (name,price) => {
  console.log('Giá ', name,':', price );
});
//> Giá  cam : 600