Một hàm gọi lại chỉ đơn giản là một hàm mà bạn truyền vào một hàm khác để hàm đó có thể gọi nó sau này. Điều này thường thấy trong các API không đồng bộ ; lệnh gọi API trả về ngay lập tức bởi vì nó không đồng bộ, vì vậy bạn chuyển một hàm vào nó mà API có thể gọi khi nó thực hiện xong tác vụ không đồng bộ của nó.
Ví dụ đơn giản nhất mà tôi có thể nghĩ đến trong JavaScript là setTimeout()
hàm. Đó là một hàm toàn cục chấp nhận hai đối số. Đối số đầu tiên là hàm gọi lại và đối số thứ hai là độ trễ tính bằng mili giây. Hàm được thiết kế để đợi một khoảng thời gian thích hợp, sau đó gọi hàm gọi lại của bạn.
setTimeout(function () {
console.log("10 seconds later...");
}, 10000);
Bạn có thể đã thấy đoạn mã trên trước đây nhưng không nhận ra rằng hàm bạn đang chuyển vào được gọi là hàm gọi lại. Chúng tôi có thể viết lại đoạn mã trên để làm cho nó rõ ràng hơn.
var callback = function () {
console.log("10 seconds later...");
};
setTimeout(callback, 10000);
Các lệnh gọi lại được sử dụng khắp nơi trong Node vì Node được xây dựng từ đầu để không đồng bộ trong mọi thứ mà nó thực hiện. Ngay cả khi nói chuyện với hệ thống tệp. Đó là lý do tại sao rất nhiều Node API nội bộ chấp nhận các hàm gọi lại làm đối số thay vì trả về dữ liệu mà bạn có thể gán cho một biến. Thay vào đó, nó sẽ gọi hàm gọi lại của bạn, truyền dữ liệu bạn muốn làm đối số. Ví dụ: bạn có thể sử dụng fs
thư viện của Node để đọc một tệp. Các fs
mô-đun lộ hai hàm API duy nhất: readFile
và readFileSync
.
Các readFile
chức năng là không đồng bộ trong khi readFileSync
rõ ràng là không. Bạn có thể thấy rằng họ dự định bạn sử dụng các cuộc gọi không đồng bộ bất cứ khi nào có thể vì họ đã gọi chúng readFile
và readFileSync
thay vì readFile
và readFileAsync
. Đây là một ví dụ về việc sử dụng cả hai hàm.
Đồng bộ:
var data = fs.readFileSync('test.txt');
console.log(data);
Đoạn mã trên chặn thực thi luồng cho đến khi tất cả nội dung của test.txt
được đọc vào bộ nhớ và được lưu trữ trong biến data
. Trong nút, điều này thường được coi là thực hành xấu. Có những lúc nó hữu ích, chẳng hạn như khi viết một đoạn script nhỏ nhanh chóng để làm một việc đơn giản nhưng tẻ nhạt và bạn không quan tâm lắm đến việc tiết kiệm từng nano giây mà bạn có thể.
Không đồng bộ (có gọi lại):
var callback = function (err, data) {
if (err) return console.error(err);
console.log(data);
};
fs.readFile('test.txt', callback);
Đầu tiên chúng ta tạo một hàm gọi lại chấp nhận hai đối số err
và data
. Một vấn đề với các hàm không đồng bộ là việc bẫy lỗi trở nên khó khăn hơn nên rất nhiều API kiểu gọi lại chuyển lỗi làm đối số đầu tiên cho hàm gọi lại. Cách tốt nhất là kiểm tra xem err
có giá trị hay không trước khi bạn làm bất cứ điều gì khác. Nếu vậy, hãy dừng thực hiện lệnh gọi lại và ghi lại lỗi.
Các cuộc gọi đồng bộ có lợi thế hơn khi có các ngoại lệ được ném ra vì bạn có thể đơn giản bắt chúng bằng một try/catch
khối.
try {
var data = fs.readFileSync('test.txt');
console.log(data);
} catch (err) {
console.error(err);
}
Trong các hàm không đồng bộ, nó không hoạt động theo cách đó. Lệnh gọi API trả về ngay lập tức nên không có gì để bắt với try/catch
. Các API không đồng bộ thích hợp sử dụng lệnh gọi lại sẽ luôn bắt lỗi riêng của chúng và sau đó chuyển các lỗi đó vào lệnh gọi lại nơi bạn có thể xử lý khi thấy phù hợp.
Ngoài các lệnh gọi lại, có một kiểu API phổ biến khác thường được sử dụng gọi là lời hứa. Nếu bạn muốn đọc về chúng thì bạn có thể đọc toàn bộ bài đăng trên blog mà tôi đã viết dựa trên câu trả lời này tại đây .