间隔在 Reason 中自行清除

Interval clearing itself in Reason

在 Reason 中,让一个间隔在满足某些条件时自行清除的最优雅的方式是什么?在 JavaScript 我可以做到:

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

In 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

setInterval/clearInterval 本质上是可变的,但即使它不是你的 fancyCondition 无论如何也是如此,所以在这里删除一个 ref 不会给你买很多。我认为即使使用 ref 它也可以通过封装进行改进,并且稍微取决于您的 fancyCondition 我们应该能够通过使用 setTimeout 而不是以纯功能方式获得相同的行为setInterval/clearInterval.

首先,让我们通过添加一个计数器、打印计数,然后在达到计数 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);

我认为我们应该做的第一件事是封装计时器 state/handle,将其包装在一个函数中,然后将 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);

这里我们稍微颠倒了这个问题。我们没有使用 setIntervalclearInterval 并将 clear 函数传递给我们的回调,而是向它传递了一个 continue 函数,该函数在我们想要继续而不是我们想要的时候调用摆脱困境。这允许我们向前传递状态,并通过使用递归来更改状态而不使用突变和 refs。而且它用更少的代码来做到这一点。我认为这可以说是相当优雅了,即使不是您所要求的那样:)