Current recommendations on fetching data (and Promises)

There have been various forum discussions on fetching data and in particular about using promises.

My understanding is that Promises and libraries such as bs-fetch is not preferred due to not having a sound type and not being cancellable. In particular this applies when using ReasonReact.

Instead using XMLHttpRequest is recommended for fetching data from the browser.

And a solution server side in Node is something still to work out.

Overall I’m assuming it probably doesn’t matter too much for most people’s use cases for now, and JavaScript developers have been using promises as the de facto strategy when fetching data for a while now (including in React).

For consistency I am considering using promises both client side and server side, but I’d be grateful to hear any recommendations for now considering some of ReScript’s future plans.

Thanks

Yes, for React apps (when using useEffect), it’s highly recommended to use some kind of cancelation mechanism, so we tend to recommend using XMLHttpRequest.

E.g. here is some example of a simple Tezos wallet demo I built end of last year that queries some TezStats data: Playground Link

We essentially bind to XMLHttpRequest and JSON.parse independently for each use-case, here is some excerpt:

type request
type response

@bs.new external makeXMLHttpRequest: unit => request = "XMLHttpRequest"
@bs.send
external addEventListener: (request, string, unit => unit) => unit =
  "addEventListener"
@bs.get external response: request => response = "response"
@bs.send external open_: (request, string, string) => unit = "open"
@bs.send external send: request => unit = "send"
@bs.send external abort: request => unit = "abort"

@bs.get external status: request => int = "status"

module Transaction = {
  // See REST Spec: https://tzstats.com/docs/api/index.html#accounts -> LIST ACCOUNT OPERATIONS
  type t = {
    row_id: float,
    receiver: option<string>,
    sender: string,
    fee: float,
    volume: float,
    time: string,
  }
  @bs.scope("JSON") @bs.val
  external parseResponse: response => array<t> = "parse"

let queryByAddress = (~address, ~onDone, ~onError, ()) => {
    let request = makeXMLHttpRequest()

    request->addEventListener("load", () =>
      onDone(request->response->parseResponse)
    )
    request->addEventListener("error", () => onError(request->status))

    request->open_(
      "GET",
      endpoint ++ ("/explorer/account/" ++ (address ++ "/operations")),
    )
    request->send

    () => request->abort
  }
}

Here, we are essentially using Transaction.parseResponse to coerce our XMLHttpRequest result into the Transaction.t type. If you want to make it type-safe, you’d wrap that parseResponse function and use a decoder accordingly.

I started some internal discussions on what to do with ReScript’s Promise story. Right now the Js.Promise bindings are in a bad shape, not only because they are pipe-last (which is easily fixable by just creating your own Promise2 bindings I guess), but because we need some kind of specialized compiler support so we can deal with the Promise nesting problem.

Even though I don’t fully agree with its APIs, reason-promise is IMO the most reliable solution on handling Promises correctly during runtime (it fixes the nesting problem), and I am even considering forking a ReScript based version off of it, just so that we have a temporary solution (with properly specified APIs) until we figure out the details.

4 Likes