#javascript #asynchronous #recursion #callback #settimeout
#javascript #асинхронный #рекурсия #обратный вызов #settimeout
Вопрос:
Почему рекурсия не остановится, если я оберну foo
в setTimeout
? Я почти уверен, что я упускаю из виду основную концепцию JavaScript об асинхронных операциях.
let foo = 0;
const bar = () => {
setTimeout(() => foo );
if (foo <= 2) {
bar();
}
}
bar();
Комментарии:
1. Обратный вызов по таймауту будет выполнен некоторое время спустя , после завершения текущей функции. Но это никогда не заканчивается.
2. @deceze на что это ссылается в it never finishes ? Кроме того, общеизвестно, что обратный вызов с таймаутом всегда выполняется через некоторое время, и я скорее искал подробное объяснение
![]()
3. Это ваша функция,
bar
. Просто очистите содержимоеsetTimeout
, по сути, не имеет значения, что вы передаетеsetTimeout
здесь. Читаяbar
с этим игнорированием, становится ясно, что это просто бесконечная рекурсия, следовательно, она никогда не заканчивается.4. @deceze Извините, что говорю, но объяснение очень расплывчатое, предлагая просто игнорировать строку кода.
5. Ну, опять же, по сути, вы можете игнорировать эту строку кода, и это ничего не изменит.
Ответ №1:
При вызове bar()
он добавляется к чему-то, называемому стеком вызовов. Стек вызовов используется для отслеживания местоположения, в котором мы находимся в нашем скрипте, когда мы вызываем и возвращаем функции. Когда вызывается функция, она добавляется в стек вызовов, а когда она возвращается, она удаляется из стека вызовов.
Stack:
- bar()
При bar()
запуске он вызывает setTimeout()
, который добавляется в стек вызовов.
setTimeout()
Функция запускает веб-API и завершает / возвращает, удаляя его из стека вызовов. Затем веб-API ожидает 0 мс (0 мс, так как при отсутствии задержки в setTimeout значение по умолчанию равно 0) и помещает / ставит в очередь ваш () => foo
обратный вызов во что-то, называемое очередью задач.
Task queue: (front ---- back)
() => foo
Задачи в очереди задач извлекаются / удаляются из очереди циклом событий только тогда, когда стек вызовов пуст. Это важно, так как это означает, что приведенный выше обратный вызов, приращения которого foo
будут вызываться только один раз, bar()
возвращается (таким образом, удаляя его из стека вызовов), однако этого никогда не происходит, поскольку bar()
он продолжает вызывать себя непрерывно, поскольку ваше условие if всегда будет истинным, и, как следствие, будет продолжать добавлятьв bar()
стек вызовов.
Stack:
- bar() // after first recursive call
- bar()
По мере того, как вы продолжаете вызывать bar()
свои рекурсивные функции, ваш стек вызовов начинает заполняться, как и ваша очередь задач:
Stack:
- bar() // after N recursive calls
...
- bar()
- bar()
Поскольку ваш стек вызовов никогда не имеет возможности вывести bar() из стека, он продолжает расти, выдавая ошибку «Превышен максимальный размер стека вызовов».
Ответ №2:
Прекратите использовать глобальное состояние и побочные эффекты, и ваши проблемы исчезнут —
const delay =
500
function bar (foo = 0)
{ if (foo > 2)
return
else
setTimeout(_ => bar(foo 1), delay)
console.log(foo)
}
bar()
Ответ №3:
Вы должны поместить рекурсию в обратный вызов setTimeout:
let foo = 0;
const bar = () => {
foo ;
if (foo <= 2) setTimeout(bar);
};
bar();
console.log(foo); // Displays 3
Комментарии:
1. Пожалуйста, не публикуйте решения вслепую даже без пробного запуска. Он будет регистрировать не 3, а 1, поскольку
setTimeout
является асинхронным , и foo будет регистрироваться до его выполнения.2. Если вы просто поместите приращение перед
bar()
вызовом, этого уже достаточно. Вводитьbar
тайм-аут или нет, тогда довольно спорно.3. Я, конечно, протестировал, но в моей консоли, которая дает мне 3: (
4. @MetallimaX поверьте мне, это невозможно
Пожалуйста, опубликуйте скриншот консоли в своем ответе.
5. @VinaySharma это возможно, потому что я ввел свой оператор console.log() после завершения обработки. Но вы правы, если вы сделаете это в скрипте, он будет выводиться до окончания «рекурсивного» setTimeout .