Вопрос проверяет понимание проблем асинхронного выполнения и состояния гонки (race condition) при использовании callback-функций.
Callback-функции — это функции, передаваемые в качестве аргументов другим функциям для выполнения после завершения какой-либо операции, часто асинхронной. Основная проблема возникает, когда callback захватывает (замыкает) переменные из внешней лексической области видимости, и эти переменные изменяются до момента вызова callback.
Классический пример — использование цикла for с асинхронными операциями внутри. Переменная цикла объявлена с помощью var, которая имеет функциональную область видимости. К моменту вызова callback-функций цикл уже завершён, и все callback ссылаются на одно и то же конечное значение переменной.
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // Всегда выводит 3
}, 100);
}Здесь setTimeout — асинхронная функция, её callback выполняется после завершения цикла. Переменная i к тому моменту равна 3, и все три callback выводят это значение.
Другая проблема — состояние гонки, когда несколько асинхронных операций пытаются изменить общие данные, и результат зависит от порядка их завершения. Если callback читает или изменяет общую переменную, конечное состояние может быть непредсказуемым.
let balance = 100;
function asyncAdd(amount, callback) {
setTimeout(() => {
balance += amount;
callback();
}, Math.random() * 100);
}
asyncAdd(10, () => console.log('Balance after first:', balance));
asyncAdd(20, () => console.log('Balance after second:', balance));
// Порядок вывода и итоговое значение balance непредсказуемы.Чтобы избежать проблем с замыканиями, можно:
let или const для блочной области видимости (в современном JavaScript).// Решение с let
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // Выводит 0, 1, 2
}, 100);
}
// Решение с IIFE
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // Выводит 0, 1, 2
}, 100);
})(i);
}Для борьбы с состоянием гонки следует использовать механизмы синхронизации, такие как промисы с async/await, очереди или мьютексы, чтобы гарантировать порядок выполнения или атомарность операций.
Вывод: Callback могут работать некорректно в асинхронном контексте из-за изменения захваченных переменных и состояний гонки. Для написания надёжного асинхронного кода важно понимать замыкания и использовать современные подходы (промисы, async/await) или тщательно изолировать данные для каждого callback.