В этом руководстве вы узнаете о функциях обратного вызова (callbacks) в JavaScript, включая синхронные и асинхронные обратные вызовы.
В JavaScript функции являются гражданами первого класса. Поэтому вы можете передать функцию другой функции в качестве аргумента.
По определению, обратный вызов — это функция, которую вы передаете другой функции в качестве аргумента для последующего выполнения.
Ниже определяется функция filter()
, которая принимает массив чисел и возвращает новый массив нечетных чисел:
function filter(numbers) { let results = []; for (const number of numbers) { if (number % 2 != 0) { results.push(number); } } return results; } let numbers = [1, 2, 4, 7, 3, 5, 6]; console.log(filter(numbers));
Как это работает:
- Сначала определите функцию
filter()
, которая принимает массив чисел и возвращает новый массив нечетных чисел. - Во-вторых, определите массив
numbers
, содержащий как нечетные, так и четные числа. - В-третьих, вызовите функцию
filter()
, чтобы получить нечетные числа из массива чисел и вывести результат.
Если вы хотите вернуть массив, содержащий четные числа, вам нужно изменить функцию filter()
. Чтобы сделать функцию filter()
более универсальной и пригодной для повторного использования, вы можете:
- Во-первых, извлеките логику из блока
if
и оберните ее в отдельную функцию. - Во-вторых, передайте функцию в функцию filter() в качестве аргумента.
Вот обновленный код:
function isOdd(number) { return number % 2 != 0; } function filter(numbers, fn) { let results = []; for (const number of numbers) { if (fn(number)) { results.push(number); } } return results; } let numbers = [1, 2, 4, 7, 3, 5, 6]; console.log(filter(numbers, isOdd));
Результат тот же. Однако вы можете передать любую функцию, которая принимает аргумент и возвращает логическое значение, во второй аргумент функции filter()
.
Например, вы можете использовать функцию filter()
для возврата массива четных чисел, например:
function isOdd(number) { return number % 2 != 0; } function isEven(number) { return number % 2 == 0; } function filter(numbers, fn) { let results = []; for (const number of numbers) { if (fn(number)) { results.push(number); } } return results; } let numbers = [1, 2, 4, 7, 3, 5, 6]; console.log(filter(numbers, isOdd)); console.log(filter(numbers, isEven));
По определению isOdd
и isEven
являются функциями обратного вызова или обратными вызовами. Поскольку функция filter()
принимает функцию в качестве аргумента, она называется функцией высокого порядка.
Обратный вызов может быть анонимной функцией, которая представляет собой функцию без имени, например:
function filter(numbers, callback) { let results = []; for (const number of numbers) { if (callback(number)) { results.push(number); } } return results; } let numbers = [1, 2, 4, 7, 3, 5, 6]; let oddNumbers = filter(numbers, function(number) { return number % 2 != 0; }); console.log(oddNumbers);
В этом примере мы передаем анонимную функцию в функцию filter()
вместо использования отдельной функции.
В ES6 вы можете использовать функцию стрелки следующим образом:
function filter(numbers, callback) { let results = []; for (const number of numbers) { if (callback(number)) { results.push(number); } } return results; } let numbers = [1, 2, 4, 7, 3, 5, 6]; let oddNumbers = filter(numbers, (number) = > number % 2 != 0); console.log(oddNumbers);
Существует два типа: синхронные и асинхронные обратные вызовы.
Синхронные обратные вызовы
Синхронный обратный вызов выполняется во время выполнения функции высокого порядка. isOdd
и isEven
являются примерами синхронных обратных вызовов, поскольку они выполняются во время выполнения функции filter()
.
Асинхронные обратные вызовы
Асинхронный выполняется после выполнения функции высокого порядка, которая использует обратный вызов.
Асинхронность означает, что если JavaScript должен дождаться завершения операции, он выполнит остальную часть кода во время ожидания.
Обратите внимание, что JavaScript — это однопоточный язык программирования. Он выполняет асинхронные операции через очередь обратного вызова и цикл обработки событий.
Предположим, вам нужно разработать скрипт, загружающий картинку с удаленного сервера и обрабатывающий ее после завершения загрузки:
function download(url) { // ... } function process(picture) { // ... } download(url); process(picture);
Однако загрузка изображения с удаленного сервера занимает время, зависящее от скорости сети и размера изображения.
Следующая функция download()
использует функцию setTimeout()
для имитации сетевого запроса:
function download(url) { setTimeout(() = > { // script to download the picture here console.log(`Downloading $ { url }...`); }, 1000); }
И этот код эмулирует функцию process()
:
function process(picture) { console.log(`Processing ${picture}`); }
Когда вы выполняете следующий код:
let url = 'https://www.javascripttutorial.net/pic.jpg'; download(url); process(url);
Вы получите следующий вывод:
Processing https://javascripttutorial.net/pic.jpg Downloading https://javascripttutorial.net/pic.jpg ...
Это не то, что вы ожидали, потому что функция process()
выполняется перед функцией download()
. Правильная последовательность должна быть:
- Скачайте картинку и дождитесь завершения загрузки.
- Обработайте картинку.
Чтобы решить эту проблему, вы можете передать функцию process()
функции download()
и выполнить функцию process()
внутри функции download()
после завершения загрузки, например:
function download(url, callback) { setTimeout(() = > { // script to download the picture here console.log(`Downloading $ { url }...`); // process the picture once it is completed callback(url); }, 1000); } function process(picture) { console.log(`Processing $ { picture }`); } let url = 'https://wwww.javascripttutorial.net/pic.jpg'; download(url, process);
Выход:
Downloading https://www.javascripttutorial.net/pic.jpg ... Processing https://www.javascripttutorial.net/pic.jpg
Теперь он работает так, как ожидалось.
В этом примере process()
— это обратный вызов, передаваемый в асинхронную функцию.
Когда вы используете обратный вызов для продолжения выполнения кода, обратный вызов называется асинхронным обратным вызовом.
Чтобы сделать код более кратким, вы можете определить функцию process()
как анонимную функцию:
function download(url, callback) { setTimeout(() = > { // script to download the picture here console.log(`Downloading $ { url }...`); // process the picture once it is completed callback(url); }, 1000); } let url = 'https://www.javascripttutorial.net/pic.jpg'; download(url, function(picture) { console.log(`Processing $ { picture }`); });
Обработка ошибок
Функция download()
предполагает, что все работает нормально и не учитывает никаких исключений. В следующем коде представлены два обратных вызова: success
и failure
для обработки случаев успеха и отказа соответственно:
function download(url, success, failure) { setTimeout(() = > { console.log(`Downloading the picture from $ { url }...`); ! url ? failure(url) : success(url); }, 1000); } download('', (url) = > console.log(`Processing the picture $ { url }`), (url) = > console.log(`The '${url}'is not valid`));
Вложенные обратные вызовы и пирамида судьбы
Как загрузить три картинки и последовательно их обработать? Типичным подходом является вызов функции download()
внутри функции обратного вызова, например:
function download(url, callback) { setTimeout(() = > { console.log(`Downloading $ { url }...`); callback(url); }, 1000); } const url1 = 'https://www.javascripttutorial.net/pic1.jpg'; const url2 = 'https://www.javascripttutorial.net/pic2.jpg'; const url3 = 'https://www.javascripttutorial.net/pic3.jpg'; download(url1, function(url) { console.log(`Processing $ { url }`); download(url2, function(url) { console.log(`Processing $ { url }`); download(url3, function(url) { console.log(`Processing $ { url }`); }); }); });
Выход:
Downloading https://www.javascripttutorial.net/pic1.jpg ... Processing https://www.javascripttutorial.net/pic1.jpg Downloading https://www.javascripttutorial.net/pic2.jpg ... Processing https://www.javascripttutorial.net/pic2.jpg Downloading https://www.javascripttutorial.net/pic3.jpg ... Processing https://www.javascripttutorial.net/pic3.jpg
Скрипт работает отлично.
Однако эта стратегия обратного вызова плохо масштабируется, когда сложность значительно возрастает.
Вложение многих асинхронных функций внутри обратных вызовов известно как пирамида гибели или ад обратных вызовов:
asyncFunction(function() { asyncFunction(function() { asyncFunction(function() { asyncFunction(function() { asyncFunction(function() {.... }); }); }); }); });
Чтобы избежать пирамиды гибели, используйте promises или функции async/await.
Заключение
- Обратный вызов — это функция, передаваемая в другую функцию в качестве аргумента для последующего выполнения.
- Функция высокого порядка — это функция, которая принимает другую функцию в качестве аргумента.
- Функции обратного вызова могут быть синхронными или асинхронными.