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