Как достичь поведения setTimeout в Elm

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

В JavaScript я использовал setTimeout(f, timeout), который явно работал очень хорошо, но - по разным причинам - я хочу избежать кода JavaScript и использовать только Elm.

Я знаю, что я могу subscribe до Tick на определенном интервале и получать тики часов, но это не то, что я хочу - у моих задержек нет разумного общего знаменателя (например, две задержки - 30 мс и 500 мс), и я хочу избежать необходимости обрабатывать много ненужных тиков.

Я также наткнулся на Task и Process - кажется, что, используя их, я как-то умею, что хочу с помощью Task.perform failHandler successHandler (Process.sleep Time.second).

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

Есть ли что-то вроде Task.delayMessage time message, которое будет делать именно то, что мне нужно (отправьте мне копию своего аргумента сообщения после указанного времени), или мне нужно сделать для этого свою собственную оболочку?

Ответ 1

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

Вот пример, который позволяет интервал мигания курсора переменной:

subscriptions : Model -> Sub Msg
subscriptions model =
    if model.showCursor
        then Time.every model.cursorBlinkInterval (always ToggleCursor)
        else Sub.none

Если я понимаю ваши проблемы, это должно преодолеть потенциал для обработки ненужных тиков. Вы можете иметь несколько подписей разных интервалов, используя Sub.batch.

Ответ 2

Если вы хотите, чтобы что-то произошло "каждые x секунд", тогда вам понравится подписное решение, как описано @ChadGilbert. (что более или менее похоже на javascript setInterval().

Если, с другой стороны, вы хотите, чтобы что-то произошло только "один раз, после x секунд", тогда маршрут Process.sleep - это путь. Это эквивалент javascript setTimeOut(): по прошествии некоторого времени он делает что-то однократно.

Вам, вероятно, придется создать свою собственную оболочку. Что-то вроде

-- for Elm 0.18
delay : Time -> msg -> Cmd msg
delay time msg =
  Process.sleep time
  |> Task.andThen (always <| Task.succeed msg)
  |> Task.perform identity

Чтобы использовать, например, например:

---
update msg model =
  case msg of
    NewStuff somethingNew ->
      ...

    Defer somethingNew ->
      model
      ! [ delay (Time.second * 5) <| NewStuff somethingNew ]

Ответ 3

Обновленная и упрощенная версия @wintvelt answer теперь:

delay : Time.Time -> msg -> Cmd msg
delay time msg =
  Process.sleep time
  |> Task.perform (\_ -> msg)

с тем же использованием