回调函数只是您传递给另一个函数的函数,以便该函数可以在以后调用它。这在异步APIs中很常见; API 调用会立即返回,因为它是异步的,因此您向其中传递了一个函数,API 可以在执行完异步任务时调用该函数。
我能想到的最简单的 JavaScript 示例是 setTimeout() 函数。这是一个接受两个参数的全局函数。第一个参数是回调函数,第二个参数是以毫秒为单位的延迟。该函数旨在等待适当的时间,然后调用您的回调函数。
setTimeout(function () {
console.log("10 seconds later...");
}, 10000);
您之前可能已经看过上面的代码,但只是没有意识到您传入的函数被称为回调函数。我们可以重写上面的代码,让它更明显。
var callback = function () {
console.log("10 seconds later...");
};
setTimeout(callback, 10000);
在 Node 中到处都使用回调,因为 Node 是从头开始构建的,它所做的一切都是异步的。即使在与文件系统交谈时。这就是为什么大量内部 Node API 接受回调函数作为参数而不是返回可以分配给变量的数据的原因。相反,它将调用您的回调函数,将您想要的数据作为参数传递。例如,您可以使用 Node 的 fs 库来读取文件。 fs 模块公开了两个独特的 API 函数:readFile 和 readFileSync。
readFile 函数是异步的,而 readFileSync 显然不是。您可以看到他们希望您尽可能使用异步调用,因为他们称它们为 readFile 和 readFileSync 而不是 readFile 和 readFileAsync。这是使用这两个函数的示例。
同步:
var data = fs.readFileSync('test.txt');
console.log(data);
上面的代码阻止线程执行,直到test.txt的所有内容都被读入内存并存储在变量data中。在节点中,这通常被认为是不好的做法。虽然有时它很有用,例如在编写一个快速的小脚本来做一些简单但乏味的事情时,您并不关心尽可能节省每一纳秒的时间。
异步(带回调):
var callback = function (err, data) {
if (err) return console.error(err);
console.log(data);
};
fs.readFile('test.txt', callback);
首先我们创建一个回调函数,它接受两个参数err 和data。异步函数的一个问题是捕获错误变得更加困难,因此许多回调风格的 API 将错误作为第一个参数传递给回调函数。最佳做法是在执行任何其他操作之前检查 err 是否具有值。如果是这样,请停止执行回调并记录错误。
同步调用在抛出异常时具有优势,因为您可以简单地使用 try/catch 块捕获它们。
try {
var data = fs.readFileSync('test.txt');
console.log(data);
} catch (err) {
console.error(err);
}
在异步函数中它不是这样工作的。 API 调用立即返回,因此try/catch 没有什么可捕获的。使用回调的适当异步 API 将始终捕获自己的错误,然后将这些错误传递到回调中,您可以在其中根据需要进行处理。
除了回调之外,还有另一种常用的 API 风格,称为 Promise。如果您想了解它们,那么您可以阅读我基于此答案 here 撰写的整篇博文。