На этом сайте есть статьи о некоторых алгоритмах сортировки и поиска решений уравнений. В них представлены фрагменты кода, которые пользователь пошагово выполняет, и анимация. Так имитируется работа трассировщика. Функционирование таких «интерпретаторов» строилось на неком допущении, которое значительно усложняло работу с рекурсивными функциями, поэтому до сего момента, например, не были рассмотрены рекурсивные сортировки.
Новое решение строится на промисах и асинхронных функциях async/await. Для примера напишем функцию factorial() для вычисления факториала числа, он определён для целых чисел: \(n!=(n-1)!\times n\), \(0! = 1, 1! = 1\).
Вызывать функцию будем по нажатию на кнопку с идентификатором start.
$('#start').click(async () => {
buttonPromise = makeButtonPromise();
const n = 5;
const result = await factorial(n);
// ...
});
В том месте функции factorial(), в котором нужно сделать паузу и дождаться, когда пользователь нажмёт на кнопку продолжения, указано await buttonPromiseWrapper();. Чтобы не перегружать пример подробностями, вызовы функций, касающихся анимации, не показаны (они могут быть расположены до и после промиса).
async function factorial(n) {
try {
await buttonPromiseWrapper();
if(n == 0 || n == 1) {
await buttonPromiseWrapper();
return 1;
}
await buttonPromiseWrapper();
return n * await factorial(n - 1)
.then(async response => {
await buttonPromiseWrapper();
return response; } );
}
catch (e) {}
}
Блок .then() добавлен для того, чтобы приостанавливать выполнение функции на обратном ходе рекурсии.
Сам промис устроен очень просто. Кнопкам с идентификаторами stepover и reset ставятся в соответствие успешное завершение ожидания и его отмена (в этом случае выбрасывается исключение, выполнение функции завершается, исключение будет поймано в самой этой функции, потому добавлен блок try/catch).
makeButtonPromise = () => {
return new Promise((resolve, reject) => {
$('#stepover').click(resolve);
$('#reset').click(reject);
})
.then(
result => {},
error => {
return Promise.reject();
});
};
Заметьте, что промис не просто создаётся и ожидается в том месте, где выполнение функции приостанавливается, а должен быть создан заранее (в том числе перед вызовом функции factorial(), см. строку buttonPromise = makeButtonPromise();). А после окончания ожидания промиса (когда пользователь нажимает на кнопку продолжения) тут же нужно создать новый промис. Благодаря этому работу функции можно полностью останавливать в любой момент, причём на всех уровнях рекурсии.
function buttonPromiseWrapper() {
await buttonPromise;
buttonPromise = makeButtonPromise();
}
Вот что получилось.