Как сделать «интерпретатор» кода на сайте

На этом сайте есть статьи о некоторых алгоритмах сортировки и поиска решений уравнений. В них представлены фрагменты кода, которые пользователь пошагово выполняет, и анимация. Так имитируется работа трассировщика. Функционирование таких «интерпретаторов» строилось на неком допущении, которое значительно усложняло работу с рекурсивными функциями, поэтому до сего момента, например, не были рассмотрены рекурсивные сортировки.

Новое решение строится на промисах и асинхронных функциях (async/await). Для примера напишем функцию factorial() для вычисления факториала числа, он определён для целых чисел: n! = (n − 1)! × n, 1! = 1, 0! = 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();
}

Вот что получилось.

 
function factorial(n)
{
if (n == 0 || n == 1)
{
return 1;
}
return factorial(n - 1) * n;
}

5 февраля · how-to · js · программирование
Оставить комментарий (отменить)