Функции обратного вызова Node.js
Node.js — это среда выполнения JavaScript, основанная на движке Chrome V8. Она позволяет JavaScript работать на стороне сервера независимо от браузера.
Одной из основных особенностей Node.js является неблокирующая модель ввода-вывода (I/O), благодаря которой Node.js отлично подходит для обработки сетевых приложений с высокой конкурентностью.
Обратные вызовы — это непосредственное воплощение асинхронного программирования в Node.js.

В Node.js функция обратного вызова — это шаблон асинхронного программирования, используемый для обработки операций ввода-вывода, таких как чтение и запись файлов, взаимодействие с базами данных и сетевые запросы. Эти операции обычно занимают много времени; при синхронной обработке они блокировали бы выполнение всей программы до завершения операции.
С помощью функций обратного вызова Node.js может продолжать выполнять другой код, пока операция ввода-вывода выполняется, а затем выполнить обратный вызов после завершения операции ввода-вывода.
Node.js является однопоточным, но реализует асинхронные операции через событийно-ориентированный механизм и механизм обратных вызовов.
Сценарии использования
- Операции ввода-вывода, такие как чтение файлов и запись файлов.
- Обработка сетевых запросов.
- Запросы к базе данных.
Например, мы можем прочитать файл и одновременно выполнять другие команды. Когда чтение файла завершено, мы возвращаем содержимое файла в качестве параметров функции обратного вызова. Таким образом, при выполнении кода не происходит блокировки или ожидания операций файлового ввода-вывода. Это значительно повышает производительность Node.js и позволяет обрабатывать большое количество одновременных запросов.
Функции обратного вызова обычно указываются в качестве последнего параметра функции:
function foo1(name, age, callback) { }
function foo2(value, callback1, callback2) { }
Пример
Пример блокирующего кода
Создайте файл input.txt со следующим содержимым:
Example官网地址:www.example.com
Создайте файл main.js со следующим кодом:
Пример
var data = fs.readFileSync('input.txt');
console.log(data.toString());
console.log("Выполнение программы завершено!");
Результат выполнения приведённого выше кода следующий:
$ node main.js Example官网地址:www.example.com 程序执行结束!
Пример неблокирующего кода
Создайте файл input.txt со следующим содержимым:
Example官网地址:www.example.com
Создайте файл main.js со следующим кодом:
Пример
fs.readFile('input.txt', function (err, data) {
if (err) return console.error(err);
console.log(data.toString());
});
console.log("Выполнение программы завершено!");
Объяснение параметров:
errЭто объект ошибки, и он будет иметь значение, если чтение не удастся.dataЭто содержимое файла, которое было прочитано.
Результат выполнения приведённого выше кода следующий:
$ node main.js 程序执行结束! Example官网地址:www.example.com
Из приведённых выше двух примеров мы можем понять разницу между блокирующими и неблокирующими вызовами:
В первом примере программа выполняется только после завершения чтения файла.
Во втором примере нам не нужно ждать завершения чтения файла, поэтому мы можем выполнять последующий код во время чтения файла, что значительно повышает производительность программы.
Следовательно, блокирующий код выполняется последовательно, а неблокирующий не требует последовательного порядка. Поэтому, если вам нужно обработать параметры функции обратного вызова, это нужно записать внутри функции обратного вызова.
Ад обратных вызовов
Когда несколько асинхронных операций должны выполняться последовательно, функции обратного вызова приводят к вложенности кода, что затрудняет чтение и поддержку кода.
Пример
if (err) {
console.error('Ошибка чтения file1:', err);
return;
}
fs.readFile('file2.txt', 'utf8', (err, data2) => {
if (err) {
console.error('Ошибка чтения file2:', err);
return;
}
fs.readFile('file3.txt', 'utf8', (err, data3) => {
if (err) {
console.error('Ошибка чтения file3:', err);
return;
}
console.log('Данные из всех файлов:', data1, data2, data3);
});
});
});
Чтобы улучшить читаемость и сопровождаемость кода, можно использовать следующие методы:
- Промисы: Промисы — это новый шаблон асинхронного программирования, который позволяет обрабатывать асинхронные операции в цепочном стиле, избегая ада обратных вызовов.
- async/await: Основан на промисах,
async/awaitОн обеспечивает асинхронный стиль программирования, который ближе к синхронному коду, что делает асинхронный код более понятным и простым в сопровождении. - Событийно-ориентированное программирование: Node.js также поддерживает событийно-ориентированное программирование, обрабатывая асинхронные операции с помощью прослушивания и генерации событий.
1. Используйте async/await
async/await — это синтаксический сахар, появившийся в ES2017, который позволяет более удобно работать с асинхронными операциями и избежать ада обратных вызовов.
Пример
async function readFiles() {
try {
const data1 = await fs.readFile('file1.txt', 'utf8');
const data2 = await fs.readFile('file2.txt', 'utf8');
const data3 = await fs.readFile('file3.txt', 'utf8');
console.log('Данные из всех файлов:', data1, data2, data3);
} catch (err) {
console.error('Ошибка чтения файлов:', err);
}
}
readFiles();
2. Используйте промисы
Промисы — это ещё один способ работы с асинхронными операциями. Вы можете связывать вызовы метода then, чтобы избежать вложенных обратных вызовов.
Пример
fs.readFile('file1.txt', 'utf8')
.then(data1 => {
console.log('Данные из file1:', data1);
return fs.readFile('file2.txt', 'utf8');
})
.then(data2 => {
console.log('Данные из file2:', data2);
return fs.readFile('file3.txt', 'utf8');
})
.then(data3 => {
console.log('Данные из file3:', data3);
})
.catch(err => {
console.error('Ошибка чтения файлов:', err);
});
Функции обратного вызова — это базовый способ обработки асинхронных операций в Node.js, но по мере роста сложности приложений проблема ада обратных вызовов становится всё более очевидной. Используя async/await или Promises, можно значительно улучшить читаемость и поддерживаемость кода.
Другие расширения