Tóm tắt: trong hướng dẫn này, bạn sẽ tìm hiểu về các hàm callback trong JavaScript bao gồm các hàm callback đồng bộ và không đồng bộ.
Các bài viết liên quan:
Callback trong javascript là một hàm được truyền vào làm tham số cho một hàm khác và được gọi lại sau khi hàm chính đã hoàn thành một công việc. Hàm callback là một cách thức để chúng ta có thể chờ đợi một hàm hoàn thành rồi mới thực hiện công việc tiếp theo.
Callback là một phương thức để xử lý bất đồng bộ trong javascript, đặc biệt là trong việc xử lý các tác vụ mạng và đọc/ghi file.
Ví dụ:
function greeting(name) { alert('Hello ' + name); } function processUserInput(callback) { var name = prompt('Please enter your name.'); callback(name); } processUserInput(greeting);
Trong ví dụ trên, hàm greeting là một callback được truyền vào hàm processUserInput và được gọi lại sau khi hàm processUserInput hoàn thành việc nhập tên người dùng.
Callback function trong JavaScript là một hàm sẽ được thực thi sau khi một hàm khác kết thúc quá trình thực thi. Hay nói cách khác sẽ là – Bất kỳ hàm nào được truyền dưới dạng đối số cho một hàm khác để nó có thể được thực thi trong hàm khác đó được gọi là hàm callback
Giả sử rằng bạn dãy số sau:
let numbers = [1, 2, 4, 7, 3, 5, 6];
Để tìm tất cả các số lẻ trong mảng, bạn có thể sử dụng phương thức filter () của đối tượng Array.
Phương thức filter () tạo một mảng mới với các phần tử vượt qua thử nghiệm được thực hiện bởi một hàm.
Hàm kiểm tra sau trả về true nếu một số là số lẻ:
function isOddNumber(number) { return number % 2; // trả về 0 nếu chẵn, 1 nếu lẻ }
Bây giờ, bạn có thể chuyển isOddNumber () vào phương thức filter ():
const oddNumbers = numbers.filter(isOddNumber); console.log(oddNumbers); // [ 1, 7, 3, 5 ]
Trong ví dụ này, isOddNumber là một callback function. Khi bạn truyền một callback vào một hàm khác, bạn chỉ cần truyền tham chiếu của hàm, tức là tên hàm mà không có dấu ngoặc đơn ().
Để làm cho nó ngắn hơn, bạn có thể sử dụng một hàm ẩn danh như một hàm callback:
let oddNumbers = numbers.filter(function(number) { return number % 2; }); console.log(oddNumbers); // [ 1, 7, 3, 5 ]
Trong ES6, bạn có thể sử dụng các hàm mũi tên:
let oddNumbers = numbers.filter(number => number % 2); // rút gọn trên ES6
Khi bạn sử dụng JavaScript trên các trình duyệt web, bạn thường nghe một sự kiện, ví dụ: một lần nhấp vào nút và thực hiện một số hành động nếu sự kiện xảy ra.
Giả sử rằng bạn có một nút với id btn:
<button id="btn">Save</button>
Để thực thi một số mã khi nút được nhấp, bạn sử dụng một lệnh gọi lại và chuyển nó vào phương thức addEventListener ():
function btnActionClicked() { // do something here } let btn = document.querySelector('#btn'); // lấy id button btn.addEventListener('click',btnActionClicked); // thêm event addEventListener
BtnClicked trong ví dụ này là một function call. Khi nút được nhấp, hàm btnClicked () được gọi để thực hiện một số hành động.
Bây giờ, bạn đã có những ý tưởng cơ bản về callback: truyền một hàm vào một hàm khác.
Các hàm Callback được sử dụng theo hai cách
Các hàm callback Synchronous(đồng bộ)
Nếu code của bạn thực thi tuần tự từ trên xuống dưới, nó là đồng bộ. Hàm isOddNumber () là một ví dụ về callback đồng bộ.
Trong ví dụ sau, hàm mũi tên là một lệnh gọi lại được sử dụng trong một hàm đồng bộ.
Phương thức sort () hoàn thành đầu tiên trước khi console.log () thực thi:
let numbers = [1, 2, 4, 7, 3, 5, 6]; numbers.sort((a, b) => a - b); console.log(numbers); // [ 1, 2, 3, 4, 5, 6, 7 ]
Các hàm Asynchronous callback
Không đồng bộ có nghĩa là nếu JavaScript phải đợi một hoạt động hoàn thành, nó sẽ thực thi phần còn lại của mã trong khi chờ đợi.
Lưu ý rằng JavaScript là một ngôn ngữ lập trình đơn luồng. Nó thực hiện các hoạt động không đồng bộ thông qua hàng đợi gọi lại và vòng lặp sự kiện.
Giả sử rằng bạn cần phát triển một tập lệnh tải xuống ảnh từ máy chủ từ xa và xử lý sau khi quá trình tải xuống hoàn tất:
function download(url) { // ... } function process(picture) { // ... } download(url); process(picture);
Tuy nhiên, việc tải ảnh từ máy chủ từ xa sẽ mất thời gian tùy thuộc vào tốc độ mạng và kích thước của ảnh.
Đoạn mã sau sử dụng hàm setTimeout () để mô phỏng hàm download ():
function download(url) { setTimeout(() => { // script tiến hành download từ url console.log(`Downloading ${url} ...`); }, 3* 1000); }
Và đoạn mã này mô phỏng hàm process ():
function process(picture) { console.log(`Processing ${picture}`);// download picture }
Khi bạn thực thi mã sau:
let url = 'https://www.website.net/zoo.jg'; download(url); process(url);
bạn sẽ nhận được kết quả sau:
Processing https: Downloading https:
Đây không phải là điều bạn mong đợi vì hàm process () thực thi trước hàm download (). Trình tự đúng phải là:
- Tải xuống hình ảnh, đợi cho đến khi hoàn thành.
- Xử lý hình ảnh.
Để khắc phục sự cố ở trên, bạn có thể chuyển hàm process () cho hàm download () và thực thi hàm process () bên trong hàm download () sau khi quá trình tải xuống hoàn tất, như sau:
function download(url, callback) { setTimeout(() => { // download url image console.log(`Downloading ${url} ...`); // tiến hành download callback(url); }, 3000); } function process(picture) { console.log(`Processing ${picture}`); // download } let url = 'https://wwww.websitehcm.com/picture.jpg'; download(url, process);
Bây giờ, nó hoạt động như mong đợi.
Trong ví dụ này, process () là một lệnh gọi lại được truyền vào một hàm không đồng bộ.
Khi bạn sử dụng lệnh callback để tiếp tục thực thi mã sau các hoạt động không đồng bộ, các lệnh gọi lại này được gọi là lệnh gọi lại không đồng bộ.
Bằng cách sử dụng lệnh gọi lại không đồng bộ, bạn có thể đăng ký trước một hành động mà không chặn toàn bộ hoạt động.
Để làm cho mã sạch hơn, bạn có thể xác định hàm process () như một hàm ẩn danh:
Sử dụng hàm Callback như thế nào ?
Sử dụng callback trong javascript có nhiều cách khác nhau, nhưng cách chung nhất là truyền hàm callback như một tham số cho hàm chính.
Ví dụ:
// Hàm callback function greeting(name) { alert('Hello ' + name); } // Hàm chính function processUserInput(callback) { var name = prompt('Please enter your name.'); callback(name); } // Gọi hàm chính với hàm callback như tham số processUserInput(greeting);
Trong ví dụ trên, hàm greeting được truyền vào hàm processUserInput như một tham số, và được gọi lại trong hàm processUserInput sau khi hoàn thành xử lý tên người dùng.
Các hàm có thể sử dụng callback với nhiều hàm callback khác nhau, và các hàm callback cũng có thể sử dụng với nhiều hàm chính khác nhau.
Callback có thể được sử dụng với các hàm có sẵn trong javascript như setTimeout, setInterval, Array.prototype.forEach, Array.prototype.map, Array.prototype.filter và hàm fetch để làm việc với mạng.
Callback cũng có thể được sử dụng với các hàm tự tạo để xử lý logic và thực hiện trực tiếp các tác vụ. Ví dụ như:
// Hàm callback function add(a, b) { return a + b; } // Hàm chính function processNumbers(num1, num2, callback) { var result = callback(num1, num2); console.log(result); } // Gọi hàm chính với hàm callback như tham số processNumbers(2, 3, add); // Output: 5
Trong ví dụ trên, hàm add được truyền vào hàm processNumbers như một tham số và được gọi lại trong hàm processNumbers để thực hiện phép cộng hai số num1 và num2.
Nói chung, sử dụng callback trong javascript là một phương thức tiên tiến để xử lý bất đồng bộ và tăng tính tái sử dụng, tối ưu hóa mã, tăng tính khả thi, tăng tính modular, tăng tính linh hoạt và tăng tính dễ bảo trì của mã. Callback có thể được sử dụng với các hàm có sẵn trong javascript như setTimeout, setInterval, Array.prototype.forEach, Array.prototype.map, Array.prototype.filter và hàm fetch để làm việc với mạng hoặc các hàm tự tạo để xử lý logic và thực hiện các tác vụ.
Trong tất cả các trường hợp, callback cần được sử dụng một cách chính xác và hợp lý để đạt được hiệu quả tối ưu.
Một số ví dụ nâng cao hơn khi sử dụng hàm Callback:
- Sử dụng callback để xử lý dữ liệu tải về từ mạng:
function getData(url, callback) { fetch(url) .then(response => response.json()) .then(data => callback(data)) .catch(error => console.log(error)); } function handleData(data) { console.log(data); } getData('https://jsonplaceholder.typicode.com/posts', handleData);
Trong ví dụ trên, hàm getData sử dụng fetch để tải dữ liệu từ một địa chỉ URL và truyền hàm callback handleData vào để xử lý dữ liệu tải về.
- Sử dụng callback để xử lý bất đồng bộ:
function asyncTask(callback) { setTimeout(() => { callback('Async task complete'); }, 2000); } function handleAsync(response) { console.log(response); } asyncTask(handleAsync); console.log('Doing other things...');
Trong ví dụ trên, hàm asyncTask sử dụng setTimeout để thực hiện một tác vụ bất đồng bộ và truyền hàm callback handleAsync vào để xử lý kết quả sau khi tác vụ hoàn thành.
- Sử dụng callback để xử lý mảng dữ liệu:
function processArray(array, callback) { var newArray = array.map(callback); console.log(newArray); } function square(num) { return num * num; } processArray([1, 2, 3, 4], square);
Trong ví dụ trên, hàm processArray sử dụng map để xử lý mảng dữ liệu và truyền hàm callback square vào để tính toán bình phương của các phần tử trong mảng.
- Sử dụng callback để xử lý dữ liệu đầu vào người dùng:
function handleForm(event, callback) { event.preventDefault(); var formData = new FormData(event.target); callback(formData); } function processData(data) { console.log(data); } document.querySelector('form').addEventListener('submit', function(event) { handleForm(event, processData); });
Trong ví dụ trên, hàm handleForm sử dụng FormData để xử lý dữ liệu đầu vào từ một form và truyền hàm callback processData vào để xử lý dữ liệu sau khi người dùng gửi form.
Những ví dụ trên chỉ là vài trong nhiều cách sử dụng callback trong javascript. Cách sử dụng callback có thể khác nhau tùy thuộc vào tình huống cụ thể.
Xử lý lỗi
Hàm download () giả định rằng mọi thứ hoạt động tốt và không xem xét bất kỳ trường hợp lỗi nào. Đoạn mã sau giới thiệu hai lệnh callback: thành công và thất bại để xử lý các trường hợp thành công và thất bại tương ứng:
function download(url, success, failure) { setTimeout(() => { // đơnload url console.log(`Downloading ${url} ...`); // nếu lỗi let error = url.length === 0 || !url; // gọi hàm xử lý lỗi error ? failure(url) : success(url); }, 3000); } download('', function(picture) { console.log(`Processing the picture ${picture}`);// đowload }, function(picture) { console.log(`Handling error...`); // xử lý lỗi } );
Callback và Pyramid of Doom
Làm thế nào để bạn tải xuống ba bức ảnh và xử lý chúng một cách tuần tự? Một cách tiếp cận điển hình là gọi hàm download () bên trong callback, như sau:
function download(url, callback) { setTimeout(() => { // tiến hành download url ở đây console.log(`Downloading ${url} ...`); // trong quá trình download callback(url); }, 3000); } const url1 = 'https://www.websitehcm.net/pic1.jpg'; const url2 = 'https://www.websitehcm.net/pic2.jpg'; const url3 = 'https://www.websitehcm.net/pic3.jpg'; download(url1,function(picture){ console.log(`Processing ${picture}`); // đơnload bức hình 2 download(url2,function(picture){ console.log(`Processing ${picture}`); // download bức hình 3 download(url3,function(picture){ console.log(`Processing ${picture}`); }); }); });
Kịch bản hoạt động hoàn toàn tốt.
Tuy nhiên, chiến lược gọi lại này không mở rộng quy mô tốt khi độ phức tạp tăng lên đáng kể.
Việc lồng nhiều hàm không đồng bộ bên trong các lệnh gọi lại được gọi là Pyramid of Doom:
asyncFunction(function(){ asyncFunction(function(){ asyncFunction(function(){ asyncFunction(function(){ asyncFunction(function(){ .... }); }); }); }); });
Để tránh Pyramid of Doom, bạn sử dụng các hàm Promise hoặc async / await.
Vì sao nên sử dụng hàm Callback ?
Sử dụng Callback trong javascript có nhiều lợi ích, bao gồm:
- Xử lý bất đồng bộ: Callback giúp cho việc xử lý bất đồng bộ trong javascript trở nên dễ dàng hơn, cho phép chúng ta thực hiện các tác vụ mạng và đọc/ghi file mà không cần phải chờ đợi cho đến khi tác vụ hoàn thành.
- Tái sử dụng mã: Sử dụng callback cho phép chúng ta tái sử dụng mã bằng cách truyền hàm như một tham số cho các hàm khác.
- Tối ưu hóa mã: Sử dụng callback giúp cho việc tối ưu hóa mã trong javascript trở nên dễ dàng hơn, giúp giảm số lượng dòng mã và giảm sự phức tạp của mã.
- Tăng tính khả thi: Sử dụng callback giúp cho việc tăng tính khả thi của mã, cho phép chúng ta thực hiện các tác vụ phức tạp hơn và độc lập hơn. Callback giúp cho việc quản lý logic và thực hiện các tác vụ phức tạp trong javascript trở nên dễ dàng hơn.
- Tăng tính modular: Sử dụng callback giúp cho việc tăng tính modular của mã, cho phép chúng ta chia nhỏ mã thành các phần nhỏ và dễ quản lý hơn.
- Tăng tính tái sử dụng: Sử dụng callback giúp cho việc tăng tính tái sử dụng của mã, cho phép chúng ta sử dụng các hàm callback nhiều lần trong mã.
- Tăng tính linh hoạt: Sử dụng callback giúp cho việc tăng tính linh hoạt của mã, cho phép chúng ta thay đổi hành vi của mã mà không cần thay đổi cấu trúc mã.
- Tăng tính dễ bảo trì: Sử dụng callback giúp cho việc tăng tính dễ bảo trì của mã, cho phép chúng ta dễ dàng thay đổi hành vi của mã mà không cần thay đổi cấu trúc mã.
Tóm lược
Gọi lại là một hàm được truyền vào một hàm khác như một đối số sẽ được thực thi sau này.
Các callback có thể đồng bộ hoặc không đồng bộ.