All Downloads are FREE. Search and download functionalities are using the official Maven repository.

japgolly.scalajs.react.callback.CallbackTo.scala Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta12
Show newest version
package japgolly.scalajs.react.callback

import japgolly.scalajs.react.callback.CallbackTo.MapGuard
import japgolly.scalajs.react.util.Effect.Sync
import japgolly.scalajs.react.util.JsUtil
import japgolly.scalajs.react.util.Trampoline
import japgolly.scalajs.react.util.Util.{catchAll, identityFn}
import java.time.{Duration, Instant}
import org.scalajs.dom.Window
import org.scalajs.dom.window
import scala.annotation.tailrec
import scala.collection.BuildFrom
import scala.concurrent.duration.{FiniteDuration, MILLISECONDS}
import scala.concurrent.{ExecutionContext, Future}
import scala.language.`3.0`
import scala.scalajs.js
import scala.scalajs.js.timers.RawTimers
import scala.scalajs.js.{Function0 => JFn0, Function1 => JFn1, UndefOr, undefined}
import scala.util.Try

object CallbackTo {

  inline def apply[A](inline f: A): CallbackTo[A] =
    lift(() => f)

  inline def lift[A](f: () => A): CallbackTo[A] =
    new CallbackTo(Trampoline.delay(f))

  inline def pure[A](a: A): CallbackTo[A] =
    new CallbackTo(Trampoline.pure(a))

  inline def throwException[A](t: Throwable): CallbackTo[A] =
    CallbackTo(throw t)

  def fromJsFn[A](f: js.Function0[A]): CallbackTo[A] =
    apply(f())

  /** Callback that isn't created until the first time it is used, after which it is reused. */
  def lazily[A](f: => CallbackTo[A]): CallbackTo[A] = {
    lazy val g = f
    suspend(g)
  }

  /** Callback that is recreated each time it is used. */
  def suspend[A](f: => CallbackTo[A]): CallbackTo[A] =
    new CallbackTo(Trampoline.suspend(() => f.trampoline))

  /** Callback that is recreated each time it is used.
    *
    * https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_name
    */
  @deprecated("Use CallbackTo.suspend", "2.0.0")
  def byName[A](f: => CallbackTo[A]): CallbackTo[A] =
    suspend(f)

  /** Tail-recursive callback. Uses constant stack space.
    *
    * Based on Phil Freeman's work on stack safety in PureScript, described in
    * [[http://functorial.com/stack-safety-for-free/index.pdf Stack Safety for
    * Free]].
    */
  def tailrec[A, B](a: A)(f: A => CallbackTo[Either[A, B]]): CallbackTo[B] =
    CallbackTo {
      @tailrec
      def go(a: A): B =
        f(a).runNow() match {
          case Left(n)  => go(n)
          case Right(b) => b
        }
      go(a)
    }

  inline def liftTraverse[A, B](f: A => CallbackTo[B]): LiftTraverseDsl[A, B] =
    new LiftTraverseDsl(f)

  final class LiftTraverseDsl[A, B](private val f: A => CallbackTo[B]) extends AnyVal {

    /** See [[CallbackTo.distFn]] for the dual. */
    inline def id: CallbackTo[A => B] =
      CallbackTo(f(_).runNow())

    /** Anything traversable by the Scala stdlib definition */
    def std[T[X] <: Iterable[X]](implicit cbf: BuildFrom[T[A], B, T[B]]): CallbackTo[T[A] => T[B]] =
      CallbackTo { ta =>
        val r = cbf.newBuilder(ta)
        ta.iterator.foreach(a => r += f(a).runNow())
        r.result()
      }

    def option: CallbackTo[Option[A] => Option[B]] =
      CallbackTo(_.map(f(_).runNow()))
  }

  /** Traverse stdlib T over CallbackTo.
    * Distribute CallbackTo over stdlib T.
    */
  def traverse[T[X] <: Iterable[X], A, B](ta: => T[A])(f: A => CallbackTo[B])(implicit cbf: BuildFrom[T[A], B, T[B]]): CallbackTo[T[B]] =
    liftTraverse(f).std[T](cbf).map(_(ta))

  /** Sequence stdlib T over CallbackTo.
    * Co-sequence CallbackTo over stdlib T.
    */
  inline def sequence[T[X] <: Iterable[X], A](inline tca: T[CallbackTo[A]])(implicit cbf: BuildFrom[T[CallbackTo[A]], A, T[A]]): CallbackTo[T[A]] =
    traverse(tca)(identityFn)(cbf)

  /** Traverse Option over CallbackTo.
    * Distribute CallbackTo over Option.
    */
  def traverseOption[A, B](oa: => Option[A])(f: A => CallbackTo[B]): CallbackTo[Option[B]] =
    liftTraverse(f).option.map(_(oa))

  /** Sequence Option over CallbackTo.
    * Co-sequence CallbackTo over Option.
    */
  inline def sequenceOption[A](inline oca: Option[CallbackTo[A]]): CallbackTo[Option[A]] =
    traverseOption(oca)(identityFn)

  /**
   * Wraps a [[Future]] so that it is repeatable, and so that its inner callback is run when the future completes.
   *
   * WARNING: Futures are scheduled to run as soon as they're created. Ensure that the argument you provide creates a
   * new [[Future]]; don't reference an existing one.
   */
  inline def future[A](f: Future[CallbackTo[A]])(implicit ec: ExecutionContext): CallbackTo[Future[A]] =
    CallbackTo(f.map(_.runNow()))

  def newJsPromise[A]: CallbackTo[(js.Promise[A], Try[A] => Callback)] =
    CallbackTo {
      val (p, f) = JsUtil.newPromise[A]()
      (p, t => Callback.fromJsFn(f(t)))
    }

  lazy val now: CallbackTo[Instant] =
    apply(Instant.now())

  lazy val currentTimeMillis: CallbackTo[Long] =
    apply(System.currentTimeMillis())

  lazy val nanoTime: CallbackTo[Long] =
    apply(System.nanoTime())

  /** When executed, opens a new window (tab) to a given URL.
    *
    * @param noopener See https://developers.google.com/web/tools/lighthouse/audits/noopener
    * @param focus    Whether or not to focus the new window.
    */
  def windowOpen(url     : String,
                 noopener: Boolean = true,
                 focus   : Boolean = true): CallbackTo[Window] =
    CallbackTo {
      val w = window.open(url, target = "_blank")
      if (noopener) w.opener = null
      if (focus) w.focus()
      w
    }

  /** Displays a dialog with an optional message prompting the user to input some text.
    *
    * @return Some string comprising the text entered by the user, or None.
    */
  def prompt: CallbackTo[Option[String]] =
    apply(Option(window.prompt()))

  /** Displays a dialog with an optional message prompting the user to input some text.
    *
    * @param message a string of text to display to the user. This parameter is optional and can be omitted if there is nothing to show in the prompt window.
    * @return Some string comprising the text entered by the user, or None.
    */
  def prompt(message: String): CallbackTo[Option[String]] =
    apply(Option(window.prompt(message)))

  /** Displays a dialog with an optional message prompting the user to input some text.
    *
    * @param message a string of text to display to the user. This parameter is optional and can be omitted if there is nothing to show in the prompt window.
    * @param default a string containing the default value displayed in the text input field. It is an optional parameter. Note that in Internet Explorer 7 and 8, if you do not provide this parameter, the string "undefined" is the default value.
    * @return Some string comprising the text entered by the user, or None.
    */
  def prompt(message: String, default: String): CallbackTo[Option[String]] =
    apply(Option(window.prompt(message, default)))

  /** Displays a modal dialog with a message and two buttons, OK and Cancel.
    *
    * @param message a string to be displayed in the dialog.
    * @return A boolean value indicating whether OK or Cancel was selected (true means OK).
    */
  def confirm(message: String): CallbackTo[Boolean] =
    apply(window.confirm(message))

  def retryUntilRight[L, R](attempt: CallbackTo[Either[L, R]])(onLeft: L => Callback): CallbackTo[R] =
    tailrec[Unit, R](())(_ => attempt.flatMap {
      case Left(e)  => onLeft(e).map(_ => Left(()))
      case Right(a) => CallbackTo pure Right(a)
    })

  /**
   * Serves as a temporary placeholder for a callback until you supply a real implementation.
   *
   * Unlike `???` this doesn't crash, it just prints a warning to the console.
   *
   * Also it's not really deprecated; that's just so you get a compiler warning as a reminder.
   */
  @deprecated("", "not really deprecated")
  def TODO[A](result: => A): CallbackTo[A] =
    Callback.todoImpl(None) >> CallbackTo(result)

  /**
   * Serves as a temporary placeholder for a callback until you supply a real implementation.
   *
   * Unlike `???` this doesn't crash, it just prints a warning to the console.
   *
   * Also it's not really deprecated; that's just so you get a compiler warning as a reminder.
   */
  @deprecated("", "not really deprecated")
  def TODO[A](result: => A, reason: => String): CallbackTo[A] =
    Callback.todoImpl(Some(() => reason)) >> CallbackTo(result)

  /**
   * Prevents `scalac` discarding the result of a map function when the final result is `Callback`.
   *
   * See https://github.com/japgolly/scalajs-react/issues/256
   *
   * @since 0.11.0
   */
  sealed trait MapGuard[A] { type Out = A }

  inline given MapGuard[A]: MapGuard[A] =
    null

  // -------------------------------------------------------------------------------------------------------------------
  // Additional ops

  extension (self: CallbackTo[Boolean]) {
    /** Creates a new callback that returns `true` when both this and the given callback return `true`. */
    def &&(b: CallbackTo[Boolean]): CallbackTo[Boolean] =
      self.map(_ && b.runNow())

    /** Creates a new callback that returns `true` when either this or the given callback return `true`. */
    def ||(b: CallbackTo[Boolean]): CallbackTo[Boolean] =
      self.map(_ || b.runNow())

    /** Negates the callback result (so long as it's boolean). */
    @deprecated("Use !cb instead of cb.!", "2.0.0")
    inline def ! : CallbackTo[Boolean] =
      self.map(!_)

    /** Negates the callback result (so long as it's boolean). */
    inline def unary_! : CallbackTo[Boolean] =
      self.map(!_)

    /** Returns a [[CallbackOption]] that requires the boolean value therein to be true. */
    def requireCBO: CallbackOption[Unit] =
      self.toCBO.flatMap(CallbackOption.require(_))
  }

  extension [A](self: CallbackTo[Option[A]]) {
    inline def asCBO: CallbackOption[A] =
      new CallbackOption(self.toScalaFn)
  }

  extension [A, B](self: CallbackTo[A => B]) {
    /** Function distribution. See `CallbackTo.liftTraverse(f).id` for the dual. */
    def distFn: A => CallbackTo[B] =
      a => self.map(_(a))
  }

  extension [A, B](self: CallbackTo[(A, B)]) {
    inline def flatMap2[C](f: (A, B) => CallbackTo[C]): CallbackTo[C] =
      self.flatMap(f.tupled)
  }

  extension [A](self: CallbackTo[A]) {
    def to[F[_]](implicit F: Sync[F]): F[A] =
      F.delay(self.runNow())
  }
}

// █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████

/**
 * A function to be executed later, usually by scalajs-react in response to some kind of event.
 *
 * The purpose of this class is to lift effects into the type system, and use the compiler to ensure safety around
 * callbacks (without depending on an external library like Cats).
 *
 * `() => Unit` is replaced by `Callback`.
 * Similarly, `ReactEvent => Unit` is replaced  by `ReactEvent => Callback`.
 *
 * @tparam A The type of result produced when the callback is invoked.
 *
 * @since 0.10.0
 */
final class CallbackTo[+A] /*private[react]*/ (private[CallbackTo] val trampoline: Trampoline[A]) extends AnyVal { self =>

  /**
    * Executes this callback, on the current thread, right now, blocking until complete.
    * Exceptions will not be thrown, not caught. Use [[CallbackTo#attempt]] to catch exceptions.
    *
    * Typically, callbacks are passed to scalajs-react and you're expected not to call this method yourself.
    * Generally speaking, the only time you should call this method is in some other non-React code's async callback.
    * Inside an AJAX callback is a common example. Even for those cases though, you can avoid calling this by getting
    * direct access instead of callback-based access to your component;
    * see the online WebSockets example: https://japgolly.github.io/scalajs-react/#examples/websockets
    *
    * While it's technically safe to call [[CallbackTo#runNow]] inside the body of another [[Callback]], it's better
    * to just use combinators like [[CallbackTo#flatMap]] or even a Scala for-comprehension.
    */
  inline def runNow(): A =
    trampoline.run

  inline def map[B](f: A => B)(using ev: MapGuard[B]): CallbackTo[ev.Out] =
    new CallbackTo(trampoline.map(f))

  /** Alias for `map`. */
  inline def |>[B](inline f: A => B)(using ev: MapGuard[B]): CallbackTo[ev.Out] =
    map(f)

  inline def flatMap[B](f: A => CallbackTo[B]): CallbackTo[B] =
    new CallbackTo(trampoline.flatMap(f(_).trampoline))

  /** Alias for `flatMap`. */
  inline def >>=[B](inline f: A => CallbackTo[B]): CallbackTo[B] =
    flatMap(f)

  inline def flatten[B](using ev: A => CallbackTo[B]): CallbackTo[B] =
    flatMap(ev)

  /** Sequence a callback to run after this, discarding any value produced by this. */
  def >>[B](runNext: CallbackTo[B]): CallbackTo[B] =
    if (isEmpty_?)
      runNext
    else
      flatMap(_ => runNext)

  /** Sequence a callback to run before this, discarding any value produced by it. */
  inline def <<[B](runBefore: CallbackTo[B]): CallbackTo[A] =
    runBefore >> this

  /** Convenient version of `<<` that accepts an Option */
  def <> this)

  /** When the callback result becomes available, perform a given side-effect with it. */
  def tap(t: A => Any): CallbackTo[A] =
    flatTap(a => CallbackTo(t(a)))

  /** Alias for `tap`. */
  inline def <|(t: A => Any): CallbackTo[A] =
    tap(t)

  def flatTap[B](t: A => CallbackTo[B]): CallbackTo[A] =
    for {
      a <- this
      _ <- t(a)
    } yield a

  def zip[B](cb: CallbackTo[B]): CallbackTo[(A, B)] =
    zipWith(cb)((_, _))

  def zipWith[B, C](cb: CallbackTo[B])(f: (A, B) => C): CallbackTo[C] =
    for {
      a <- this
      b <- cb
    } yield f(a, b)

  /** Alias for `>>`.
    *
    * Where `>>` is often associated with Monads, `*>` is often associated with Applicatives.
    */
  inline def *>[B](inline runNext: CallbackTo[B]): CallbackTo[B] =
    >>(runNext)

  /** Sequence actions, discarding the value of the second argument. */
  def <*[B](next: CallbackTo[B]): CallbackTo[A] =
    flatTap(_ => next)

  /** Discard the callback's return value, return a given value instead.
    *
    * `ret`, short for `return`.
    */
  def ret[B](b: B): CallbackTo[B] =
    this >> CallbackTo.pure(b)

  /** Discard the value produced by this callback. */
  def void: Callback =
    this >> Callback.empty

  /** Discard the value produced by this callback.
    *
    * This method allows you to be explicit about the type you're discarding (which may change in future).
    */
  inline def voidExplicit[B](using inline ev: A <:< B): Callback =
    void

  /** Wraps this callback in a try-catch and returns either the result or the exception if one occurs. */
  def attempt: CallbackTo[Either[Throwable, A]] =
    CallbackTo(
      try Right(runNow())
      catch { case t: Throwable => Left(t) }
    )

  def attemptTry: CallbackTo[Try[A]] =
    CallbackTo(catchAll(runNow()))

  def handleError[AA >: A](f: Throwable => CallbackTo[AA]): CallbackTo[AA] =
    CallbackTo[AA](
      try runNow()
      catch { case t: Throwable => f(t).runNow() })

  def maybeHandleError[AA >: A](f: PartialFunction[Throwable, CallbackTo[AA]]): CallbackTo[AA] =
    CallbackTo[AA](
      try runNow()
      catch {
        case t: Throwable => f.lift(t) match {
          case Some(c) => c.runNow()
          case None    => throw t
        }
      })

  /** If this completes successfully, discard the result.
    * If any exception occurs, call `printStackTrace` and continue.
    *
    * @since 2.0.0
    */
  def reset: Callback =
    Callback(
      try {
        runNow()
        ()
      } catch {
        case t: Throwable =>
          t.printStackTrace()
      }
    )

  /** Return a version of this callback that will only execute once, and reuse the result for all
    * other invocations.
    */
  def memo(): CallbackTo[A] = {
    var result: Option[Try[A]] = None
    val real = attemptTry
    CallbackTo {
      val t: Try[A] =
        result.getOrElse {
          val t = real.runNow()
          result = Some(t)
          t
        }
      t.get
    }
  }

  /** Conditional execution of this callback.
    *
    * @param cond The condition required to be `true` for this callback to execute.
    * @return `Some` result of the callback executed, else `None`.
    */
  inline def when(inline cond: Boolean): CallbackTo[Option[A]] =
    CallbackTo(if (cond) Some(runNow()) else None)

  /** Conditional execution of this callback.
    * Reverse of [[when()]].
    *
    * @param cond The condition required to be `false` for this callback to execute.
    * @return `Some` result of the callback executed, else `None`.
    */
  inline def unless(inline cond: Boolean): CallbackTo[Option[A]] =
    when(!cond)

  /** Conditional execution of this callback.
    * Discards the result.
    *
    * @param cond The condition required to be `true` for this callback to execute.
    */
  inline def when_(inline cond: Boolean): Callback =
    CallbackTo(if (cond) runNow())

  /** Conditional execution of this callback.
    * Discards the result.
    * Reverse of [[when_()]].
    *
    * @param cond The condition required to be `false` for the callback to execute.
    */
  inline def unless_(inline cond: Boolean): Callback =
    when_(!cond)

  /** Limits the number of invocations in a given amount of time.
    *
    * @return Some if invocation was allowed, None if rejected/rate-limited
    */
  inline def rateLimit(inline window: Duration): CallbackTo[Option[A]] =
    rateLimitMs(window.toMillis)

  /** Limits the number of invocations in a given amount of time.
    *
    * @return Some if invocation was allowed, None if rejected/rate-limited
    */
  inline def rateLimit(inline window: FiniteDuration): CallbackTo[Option[A]] =
    rateLimitMs(window.toMillis)

  /** Limits the number of invocations in a given amount of time.
    *
    * @return Some if invocation was allowed, None if rejected/rate-limited
    */
  inline def rateLimit(inline window: Duration, inline maxPerWindow: Int): CallbackTo[Option[A]] =
    rateLimitMs(window.toMillis, maxPerWindow)

  /** Limits the number of invocations in a given amount of time.
    *
    * @return Some if invocation was allowed, None if rejected/rate-limited
    */
  inline def rateLimit(inline window: FiniteDuration, inline maxPerWindow: Int): CallbackTo[Option[A]] =
    rateLimitMs(window.toMillis, maxPerWindow)

  /** Limits the number of invocations in a given amount of time.
    *
    * @return Some if invocation was allowed, None if rejected/rate-limited
    */
  inline def rateLimitMs(windowMs: Long, maxPerWindow: Int = 1): CallbackTo[Option[A]] =
    if (windowMs <= 0 || maxPerWindow <= 0)
      CallbackTo.pure(None)
    else
      CallbackTo.lift(RateLimit.fn0(
        run          = toScalaFn,
        maxPerWindow = maxPerWindow,
        windowMs = windowMs,
      ))

  /** Creates an debounce boundary over the underlying computation.
    *
    * Save the result of this as a `val` somewhere because it relies on internal state that must be reused.
    */
  inline def debounce(inline delay: Duration): Callback =
    __debounceMs(delay.toMillis)

  /** Creates an debounce boundary over the underlying computation.
    *
    * Save the result of this as a `val` somewhere because it relies on internal state that must be reused.
    */
  inline def debounce(inline delay: FiniteDuration): Callback =
    __debounceMs(delay.toMillis)

  /** Creates an debounce boundary over the underlying computation.
    *
    * Save the result of this as a `val` somewhere because it relies on internal state that must be reused.
    */
  inline def debounceMs(inline delayMs: Long): Callback =
    __debounceMs(delayMs)

  private[react] inline def __debounceMs(delayMs: Long): Callback =
    if (delayMs <= 0)
      void
    else
      _debounceMs(delayMs)

  private[react] def _debounceMs(delayMs: Long)(implicit timer: Timer): Callback = {
    var prev = Option.empty[timer.Handle]
    CallbackTo {
      prev.foreach(timer.cancel)
      val newHandle = timer.delay(delayMs) {
        prev = None
        self.runNow()
      }
      prev = Some(newHandle)
    }
  }

  /** Log to the console before this callback starts, and after it completes.
    *
    * Does not change the result.
    */
  def logAround(message: Any, optionalParams: Any*): CallbackTo[A] = {
    def log(prefix: String) = Callback.log(prefix + message, optionalParams: _*)
    log("→  Starting: ") *> self <* log(" ← Finished: ")
  }

  /** Logs the result of this callback as it completes. */
  def logResult(msg: A => String): CallbackTo[A] =
    flatTap(a => Callback.log(msg(a)))

  /** Logs the result of this callback as it completes.
    *
    * @param name Prefix to appear the log output.
    */
  def logResult(name: String): CallbackTo[A] =
    logResult(a => s"$name: $a")

  /** Logs the result of this callback as it completes. */
  def logResult: CallbackTo[A] =
    logResult(_.toString)


  /** Convenience-method to run additional code after this callback. */
  inline def thenRun[B](inline runNext: B)(using ev: MapGuard[B]): CallbackTo[ev.Out] =
    this >> CallbackTo(runNext)

  /** Convenience-method to run additional code before this callback. */
  inline def precedeWith(inline runFirst: Unit): CallbackTo[A] =
    this << Callback(runFirst)

  /** Wraps this callback in a `try-finally` block and runs the given callback in the `finally` clause, after the
    * current callback completes, be it in error or success.
    */
  def finallyRun[B](runFinally: CallbackTo[B]): CallbackTo[A] =
    CallbackTo(try runNow() finally runFinally.runNow())

  inline def toScalaFn: () => A =
    () => runNow()

  /** The underlying representation of this value-class */
  inline def underlyingRepr: Trampoline[A] =
    trampoline

  inline def toJsFn: JFn0[A] =
    toScalaFn

  inline def toJsFn1: JFn1[Any, A] =
    (_: Any) => runNow()

  def toJsCallback: UndefOr[JFn0[A]] =
    if (isEmpty_?) undefined else toJsFn

  inline def isEmpty_? : Boolean =
    trampoline eq Callback.empty.trampoline

  /** Turns this into an [[AsyncCallback]] that runs whenever/wherever it's called;
    * `setTimeout` isn't used.
    *
    * In order words, `this.toAsyncCallback.toCallback` == `this`.
    */
  def asAsyncCallback: AsyncCallback[A] =
    AsyncCallback(attemptTry.flatMap(_))

  /** Schedules this to run asynchronously (i.e. uses a `setTimeout`).
    *
    * Exceptions will be handled by the [[AsyncCallback]] such that
    * `this.async.toCallback` will never throw an exception.
    */
  inline def async: AsyncCallback[A] =
    delayMs(1)

  /** Run asynchronously after a delay of a given duration. */
  inline def delay(inline startIn: FiniteDuration): AsyncCallback[A] =
    asAsyncCallback.delay(startIn)

  /** Run asynchronously after a delay of a given duration. */
  inline def delay(inline startIn: Duration): AsyncCallback[A] =
    asAsyncCallback.delay(startIn)

  /** Run asynchronously after a `startInMilliseconds` ms delay. */
  inline def delayMs(inline startInMilliseconds: Double): AsyncCallback[A] =
    asAsyncCallback.delayMs(startInMilliseconds)

  /** Record the duration of this callback's execution. */
  def withDuration[B](f: (A, FiniteDuration) => CallbackTo[B]): CallbackTo[B] = {
    def nowMS: Long = System.currentTimeMillis()
    CallbackTo {
      val s = nowMS
      val a = runNow()
      val e = nowMS
      val d = FiniteDuration(e - s, MILLISECONDS)
      f(a, d).runNow()
    }
  }

  /** Log the duration of this callback's execution. */
  def logDuration(fmt: FiniteDuration => String): CallbackTo[A] =
    withDuration((a, d) =>
      Callback.log(fmt(d)) ret a)

  /** Log the duration of this callback's execution.
    *
    * @param name Prefix to appear the log output.
    */
  def logDuration(name: String): CallbackTo[A] =
    logDuration(d => s"$name completed in $d.")

  /** Log the duration of this callback's execution. */
  def logDuration: CallbackTo[A] =
    logDuration("Callback")

  def toCBO: CallbackOption[A] =
    map[Option[A]](Some(_)).asCBO

  /** Schedule for repeated execution every `interval`. */
  def setInterval(interval: Duration): CallbackTo[Callback.SetIntervalResult] =
    setIntervalMs(interval.toMillis.toDouble)

  /** Schedule for repeated execution every `interval`. */
  def setInterval(interval: FiniteDuration): CallbackTo[Callback.SetIntervalResult] =
    setIntervalMs(interval.toMillis.toDouble)

  /** Schedule this callback for repeated execution every `interval` milliseconds.
    *
    * @param interval duration in milliseconds between executions
    * @return A means to cancel the interval.
    */
  def setIntervalMs(interval: Double): CallbackTo[Callback.SetIntervalResult] = {
    val underlying = self.toJsFn
    CallbackTo {
      val handle = RawTimers.setInterval(underlying, interval)
      new Callback.SetIntervalResult(handle)
    }
  }

  /** Schedule this callback for execution in `interval`.
    *
    * Note: in most cases [[delay()]] is a better alternative.
    *
    * @return A means to cancel the timeout.
    */
  def setTimeout(interval: Duration): CallbackTo[Callback.SetTimeoutResult] =
    setTimeoutMs(interval.toMillis.toDouble)

  /** Schedule this callback for execution in `interval`.
    *
    * Note: in most cases [[delay()]] is a better alternative.
    *
    * @return A means to cancel the timeout.
    */
  def setTimeout(interval: FiniteDuration): CallbackTo[Callback.SetTimeoutResult] =
    setTimeoutMs(interval.toMillis.toDouble)

  /** Schedule this callback for execution in `interval` milliseconds.
    *
    * Note: in most cases [[delayMs()]] is a better alternative.
    *
    * @param interval duration in milliseconds to wait
    * @return A means to cancel the timeout.
    */
  def setTimeoutMs(interval: Double): CallbackTo[Callback.SetTimeoutResult] = {
    val underlying = self.toJsFn
    CallbackTo {
      val handle = RawTimers.setTimeout(underlying, interval)
      new Callback.SetTimeoutResult(handle)
    }
  }

  /** Runs this computation in the background. */
  def dispatch: Callback =
    asAsyncCallback.fork_

  def withFilter(f: A => Boolean): CallbackTo[A] =
    map[A](a => if f(a) then a else
      // This is what scala.Future does
      throw new NoSuchElementException("CallbackTo.withFilter predicate is not satisfied"))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy