Как убедиться, что рекурсивная функция больше не вызывается до ее возвращения

0

Я делаю RPG в холсте JavaScript и HTML5. В диалоговом окне я добавил эффект "ввода", чтобы отобразить диалоговое окно NPC автоматически (буква за буквой).

Эта функция запускается, когда пользователь нажимает кнопку. Хотя функция повторяется через string.length, добавляя каждый символ в указанный индекс, я не хочу, чтобы пользователь мог снова нажать кнопку до тех пор, пока функция не остановится (доходит до конечного индекса строки).

Для этого я добавил $("#responseButton").prop('disabled', true); в то время как функция повторяется, а $("#responseButton").prop('disabled', false); когда он индексирует диалог string.length.

Проблема. Кажется, что кнопка отключена, как ожидалось, в то время как функция повторяется, но как только это будет сделано, я могу продолжать нажимать на активированную кнопку... функция, кажется, называется еще несколько раз при каждом нажатии кнопки... даже если повторение из ранее названного не закончилось... это приводит к тому, что текст постоянно перезаписывается.

Вопрос: Как я могу гарантировать, что функция повторяется, прежде чем ее можно будет снова вызвать? Это похоже на время задержки, если я продолжаю нажимать кнопку до тех пор, пока она снова не запустится...

typeEffect : function (index) {     
    if (index >= game.data.NPCdialog.length) {
        game.data.enableCycle = true;
        $("#responseButton").prop('disabled', false);
        return;
    }
    else{
        $("#responseButton").prop('disabled', true); //wait until typing is done before user can press again
        $("#npc_dialog").append(game.data.NPCdialog.charAt(index++));
        var t = setTimeout('Utilities.typeEffect('+ (index) +')', 40);      
    }       
},
Теги:
recursion

3 ответа

1

Как уже упоминалось, часть, которая вас кусает, представляет собой комбинацию реакции событий, асинхронности и рекурсивной функции, которая не может быть связана с тем, как и когда вы ожидаете. Это идет немного дальше, но это достаточно, чтобы проиллюстрировать, где все идет не так, не вдаваясь в мета-программирование между трещинами JS и движком браузера.

Теперь, когда другие ответы показали вам, почему это не работает так, как вы думаете, я бы предположил, что корень проблемы в том, что вы пытаетесь управлять тем, что должно произойти ровно один раз (переключая кнопку) внутри то, что может произойти сто раз, где, если вы написали немного больше кода и вывернули его наизнанку, было бы намного легче визуализировать.

Возможно, рефакторинг, который выглядит немного так, может сделать вещи проще:

var button = {
    el : $("#responseButton"), // cache your references
    setDisabled : function (state) { button.el.prop("disabled", state); },
    enable  : function () { button.setDisabled(false); },
    disable : function () { button.setDisabled(true ); },
    handleClick : function () {
        button.disable();
        Utilities.typeEffect(0, function () { button.enable(); });
    }
},

dialogBox = {
    // if you're going to call this 100x in a row, cache it
    el     : $("#npc_dialog"),
    append : function (text) { dialogBox.el.append(text); }
},

sec = 1000,
textSpeed = {
    fast   : 1/25 * sec, // 25x /sec
    medium : 1/10 * sec, // 10x /sec
    slow   : 1/ 5 * sec  //  5x /sec
};

button.el.on("click", button.handleClick);



Utilities.typeEffect = function (index, done) {
    if (index >= game.data.NPCdialog.length) {
        game.data.enableCycle = true;
        return done();
    } else {
        var character = game.data.NPCdialog[index];
        dialogBox.append(character);
        setTimeout(function () {
            Utilities.typeEffect(index+1, done);
        }, textSpeed.fast);
    }
};

Вы должны заметить, что я очень сильно изменился внутри фактического .typeEffect.
То, что я сделал, это построить небольшую небольшую button, содержащую кешированную ссылку на элемент (не заставляйте jQuery искать его на своей странице 25 раз в секунду) добавляет некоторые быстрые вспомогательные методы...... и затем я дал кнопке управление ее обработкой событий, в том числе зная, как очистить, когда придет время.

Если вы заметите, я дал .typeEffect один параметр, done, и аргумент, который я передал, - это функция, определяемая button, которая знает, как очистить себя, когда все будет закончено. Функция не больше, и все же я сейчас выполняю блокировку ввода/разблокирования ровно один раз, передавая обратный вызов.

Еще лучше, я могу гарантировать, что он заблокирован непосредственно перед началом рекурсии, и он разблокируется сразу после последней рекурсии, без какого-либо плохо определенного состояния, чтобы мешать.

Я также кэшировал диалоговое окно, чтобы снова ускорить процесс;
В наши дни jQuery творит чудеса, но это все равно нужно кэшировать.

Если бы я принял его дальше, я бы, вероятно, сделал typeEffect плагином для компонента для написания диалога, чтобы можно было typeEffect от нескольких различных типов эффектов.
Я бы, вероятно, также начал передавать рекурсивную функцию другим аргументам, которые она волнует (скорость текста, ссылка на диалоговое окно и т.д.), Так что их легче заменить, и функция становится более многоразовой (и проще чтобы увидеть, что он работает).

Обратите внимание, что на самом деле это не ответ. Это решение, которое должно не только решить эту проблему, но иллюстрирует, как предотвратить это в будущем.
Если он работает, используйте его, если нет, не делайте этого.
Если концепция работает, но это не ваш стиль, а затем измените ее (хотя, избегайте использования строки в setTimeout..., которая должна перестать преподаваться годами и годами назад; убийца производительности, опасность для безопасности, если вы затираете созданные пользователем строки, легко сделать нестрогие ошибки в контексте eval-context...... все хорошие вещи).

  • 0
    Норгард, как еще можно имитировать эффект печати без задержки setTimeout?
  • 0
    @Growler вам требуется setTimeout или, альтернативно, setImmediate или requestAnimationFrame , чтобы получить setImmediate эффект в JavaScript (другие решения могут полностью заблокировать окно браузера). setTimeout("doStuff(data);", 50); следует избегать, так это строки внутри: setTimeout("doStuff(data);", 50); , Вместо этого передайте ему функцию: setTimeout(function () { doStuff(data); }, 50); , Это быстрее, безопаснее и более предсказуемо.
1

Проблема в том, что setTimeout возвращается немедленно, а затем ваш код продолжается.

Вам нужна функция, которая вызывает себя после таймаута и проверяет, должна ли кнопка быть включена.

Вот демо: http://cdpn.io/Idypu

  • 0
    По какой-то причине это все еще не решает мою проблему. Я могу продолжать нажимать на кнопку после того, как она снова станет активной, и есть задержка, когда она снова и снова выполняет вызов
1

Javascript является однопоточным. Если вы не намеренно отказываетесь от контроля, никакая другая функция Javascript не может быть вызвана.

РЕДАКТИРОВАТЬ

Норгард пишет:

конечно, вы видите setTimeout, там

Я не видел setTimeout и перестал называть меня Ширли.

И я думаю, что его план будет работать, за исключением того, что несколько событий помещаются в очередь до того, как обработчик будет запущен.

Growler, я думаю, что проблема, с которой вы сталкиваетесь, заключается в том, что вы предполагаете, что обработчик будет запущен, а кнопка отключена после первого щелчка, и поэтому последующие события не могут быть сгенерированы. Это, я считаю, неверно.

Вместо этого попробуйте следующее:

typeEffect : function() {
  var enabled = true;

  var process = function(index) {
    if (index >= game.data.NPCdialog.length) {
      game.data.enableCycle = true;
      enabled = true;
      $("#responseButton").prop('disabled', false);
    }
    else {
      $("#npc_dialog").append(game.data.NPCdialog.charAt(index));
      setTimeout(function() {
        process(index+1);
      }, 40);
    }
  };

  return function(index) {     
    if (enabled) {
      $("#responseButton").prop('disabled', true);
      process(index);
    }
  };
}(),
  • 0
    конечно, вы видите setTimeout, там.
  • 0
    ... похоже, я выбрал не ту неделю, чтобы перестать нюхать клей. Тем не менее, я считаю, что это будет .charAt(index) а затем process(index+1) , из-за того, когда постфикс будет разрешен. .charAt(++index) будет .charAt(index+1) .
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню