5

Suppose I have a controlled Elmish form:

type Model = 
  {
    Query : string
    IsLoading : bool
    Result : Result<QueryResults, string> option
  }

type Message =
  | UpdateQuery of string
  | ReceivedResults of Result<QueryResults, string>

let update message model = 
  match message with
  | UpdateQuery query ->
    let nextModel =
      {
        model with
          Query = query
          IsLoading = true
      }

    let cmd = 
      Cmd.OfAsync.result (async {
        let! results = Api.tryFetchQueryResults query

        return ReceivedResults results
      })

    nextModel, cmd
  | ReceivedResults results ->
    {
      model with
        IsLoading = false
        Results = Some results
    }, Cmd.none

Every time model.Query changes, it sends off an async request. However, if there is already a request in progress, I would like that to be cancelled and replaced with the new one.

What is a good way to do this in Elmish?

sdgfsdh
  • 33,689
  • 26
  • 132
  • 245

1 Answers1

2

There is nothing built in to support cancellation of the underlying XMLHttpRequest.

We can, however, build some small machinery that will support cancellation-like functionality:

First, augment your Model with information about the current pending query, and your Message with a link (a simple Guid will do) to that query

open System

type Model = 
  {
    Query : string
    IsLoading : bool
    Result : Result<QueryResults, string> option
    PendingQuery: Guid option
  }

type Message =
  | UpdateQuery of string
  | ReceivedResults of Guid * Result<QueryResults, string>

Next, provide these pieces of data in your update function

  | UpdateQuery query ->

    let correlationId = Guid.NewGuid()

    let nextModel =
      {
        model with
          Query = query
          IsLoading = true
          PendingQuery = Some correlationId
      }

    let cmd = 
      Cmd.OfAsync.result (async {
        let! results = Api.tryFetchQueryResults query

        return ReceivedResults (correlationId, results)
      })

    nextModel, cmd

Finally, check the pending query when receiving data from the server and ignore stuff not matching the link

  | ReceivedResults (correlationId, results) ->
    match model.PendingQuery with
    | Some id when id = correlationId ->
      {
        model with
          IsLoading = false
          PendingQuery = None
          Result = Some results
      }, Cmd.none
    | _ -> model, Cmd.none
nilekirk
  • 2,353
  • 1
  • 8
  • 9
  • This ignores "stale" requests, but how do I actually cancel them? – sdgfsdh Dec 26 '20 at 22:03
  • To cancel, you need to get hold of the underlying XMLHttpRequest, and call abort() on that. But the XMLHttpRequest is not surfaced through the Fable.Remoting API. – nilekirk Dec 27 '20 at 06:18
  • I have access to cancellation via `Async.StartAsPromise`, which takes a cancellation token. My question is more, how I should integrate / track the cancellation token with Elmish – sdgfsdh Dec 27 '20 at 21:14