Интервал проясняется в Reason - PullRequest
0 голосов
/ 16 мая 2018

В Reason, какой был бы самый изящный способ иметь интервал, который очищает себя, когда некоторое условие выполнено? В JavaScript я мог бы сделать:

var myInterval = setInterval(function () {
    // do some stuff
    if (fancyCondition) {
        clearInterval(myInterval);
    }
}, 1000);

В Reason лучшее, что я придумал, это:

let intervalIdRef = ref(None);
let clearInterval = () =>
    switch (intervalIdRef^) {
    | Some(intervalId) => Js.Global.clearInterval(intervalId)
    | None => ()
    };
let intervalId = Js.Global.setInterval(() => {
    /* do some stuff */
    fancyCondition ? clearInterval() : ();
}, 1000);
intervalIdRef := Some(intervalId);

Есть ли способ избежать использования ref?

1 Ответ

0 голосов
/ 19 мая 2018

setInterval / clearInterval по своей природе изменчив, но даже если это не ваш fancyCondition, в любом случае, удаление этого ref не будет вам дорого стоить.Я думаю, что даже с ref он может улучшиться за счет инкапсуляции, и, в зависимости от вашего fancyCondition, мы сможем получить такое же поведение чисто функциональным способом, используя setTimeout вместо setInterval / * 1009.*.

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

let intervalIdRef = ref(None);
let count = ref(0);

let clearInterval = () =>
  switch (intervalIdRef^) {
    | Some(intervalId) => Js.Global.clearInterval(intervalId)
    | None => ()
  };

let intervalId = Js.Global.setInterval(() => {
  if (count^ < 5) {
    Js.log2("tick", count^);
    count := count^ + 1;
  } else {
    Js.log("abort!");
    clearInterval();
  }
}, 200);

intervalIdRef := Some(intervalId);

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

let setInterval = (timeout, action) => {
  let intervalIdRef = ref(None);
  let clear = () =>
    switch (intervalIdRef^) {
      | Some(intervalId) => Js.Global.clearInterval(intervalId)
      | None => ()
    };

  let intervalId = Js.Global.setInterval(() => action(~clear), timeout);
  intervalIdRef := Some(intervalId);
};

let count = ref(0);
setInterval(200, (~clear) => {
  if (count^ < 5) {
    Js.log2("tick", count^);
    count := count^ + 1;
  } else {
    Js.log("abort!");
    clear();
  }
});

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

let rec setTimeout = (timeout, action, state) => {
  let continue = setTimeout(timeout, action);
  let _:Js.Global.timeoutId =
    Js.Global.setTimeout(() => action(~continue, state), timeout)
};

setTimeout(200, (~continue, count) => {
  if (count < 5) {
    Js.log2("tick", count);
    continue(count + 1);
  } else {
    Js.log("abort!");
  }
}, 0);

Здесь мы немного перевернули проблему.Вместо использования setInterval и clearInterval и передачи функции clear в наш обратный вызов мы передаем ей функцию continue, которая вызывается, когда мы хотим продолжить, а не когда мы хотим выручить.Это позволяет нам передавать состояние вперед и изменять состояние без использования мутации и ref s, используя вместо этого рекурсию.И это происходит с меньшим количеством кода.Я думаю, что это будет выглядеть довольно элегантно, если не совсем то, что вы просили:)

...