How do I perform HTTP requests

This may be a very beginner question with an obvious answer, but I am new to ReScript and I would like to know how do I perform HTTP requests.

I didn’t find anything related to this in the documentation.

Thank you in advance.

Something like this? These are bindings to the native browser fetch API.

this is probably a good example to add into the docs, maybe under the newcomer examples section?

1 Like

Thank you @thislogancall.

I’ll look into it.

So there is no “native” way of doing that, I understand.

The Fetch API is the native way of making HTTP requests in JavaScript, and ReScript is a very lightweight layer on top of JavaScript. We normally don’t ship pure-ReScript reimplementations of libraries that already exist on the platform.

1 Like

Tried the bs-fetch with their example:

Js.Promise.(
  Fetch.fetch("/api/hellos/1")
    |> then_(Fetch.Response.text)
    |> then_(text => print_endline(text) |> resolve)
  );

Got compilations errors.

Checked the Docs for Promise. The example in the Docs uses |> instead of ->.

My code looks like this:

Js.Promise.make((~resolve, ~reject) => {
  Fetch.fetch("https://api.icndb.com")
    ->then_(Fetch.Response.text)
    ->then_(text => print_endline(text) |> resolve)
});

Still compilation errors…

image

I’d appreciate any help.

Notice in the original example code:

Js.Promise.(
  ...
);

These outer parentheses open the Js.Promise module inside their scope and make all the module’s contents visible, including the then_ function. If you copy the given code exactly, and use Reason syntax (.re file), it will work.

Alternatively, you can convert it into ReScript syntax, but I would recommend holding off on ReScript syntax for the time being.

Or maybe use the fully qualified module names everywhere, which is less confusing for beginners:

  Fetch.fetch("https://api.icndb.com")
  |> Js.Promise.then_(Fetch.Response.text)
  |> Js.Promise.then_(text => print_endline(text) |> Js.Promise.resolve)

Edit: Note that you can interchange -> and |> only in one-parameter functions, because |> puts the input from the left hand side into the last parameter of the function on the right hand side and ->
puts the input from the left hand side into the first parameter of the function on the right hand side.

You can still use -> everywhere with this trick, though:

Fetch.fetch("https://api.icndb.com")
->Js.Promise.then_(Fetch.Response.text, _)
->Js.Promise.then_(text => print_endline(text)->Js.Promise.resolve, _);

The _ placeholder will ensure that the parameter on the left hand side of the -> is put in the position defined by you, in this case in the last position. (And Js.Promise.resolve here can be just fed with -> because it is a one-parameter-function).

2 Likes

Oops, disregard the earlier version of this message. I’m actually confused why Js.Promise.make is being used here. A fetch response is a promise anyway…

1 Like

Yes, I updated my answer accordingly. This got me also confused.

1 Like

Thank you all for the replies.

@fham, the _ placeholder seems to resolve the errors. Thank you. Although I don’t understand why then_ needs 2 functions and what are they exactly.

In a functional language we discourage attaching functions to data values; the data is instead passed into a method. This style is applied to JavaScript interop as well, which is why we have two arguments for then_(function, promise). When compiled to JavaScript, the interop logic swaps it back to the JavaScript style promise.then(function). This way developing code all looks the same but still produces the correct runtime code.

If you look at the JavaScript output from this code:

Fetch.fetch("https://api.icndb.com")
->Js.Promise.then_(Fetch.Response.text, _)
->Js.Promise.then_(text => print_endline(text)->Js.Promise.resolve, _);

will compile to the equivalent of

fetch("https://api.icndb.com")
  .then((x) => x.text)
  .then((text) => Promise.resolve((console.log(text))

in JavaScript. It won’t be quite that clean because the output is still ES5, but the result is the same.

2 Likes

We highly recommend just using plain XMLHTTPRequest over bs-fetch. Example:

// shape of your response
@bs.scope("JSON") @bs.val
external parseResponse: response => {"message": array<string>} = "parse"

let request = makeXMLHttpRequest()
request->addEventListener("load", () => {
  let response = request->response->parseResponse
  Js.log(response["message"])
})
request->open_("GET", "https://dog.ceo/api/breeds/image/random/3");
request->send

Here’s a gist with the old Reason syntax too: https://gist.github.com/chenglou/251ba7e6aa7c86c6ffdb301f420e934d

Benefits:

  • No runtime
  • Proper cancellation and proper error handlings (see gist). Network programming is about error recovery, not about happy paths.
  • Doesn’t need an extra library
  • No need to drag in Promise
  • Supported everywhere
  • Recommended with ReasonReact (as opposed to anything Promise-based, which are heavily discouraged in ReasonReact and Reactjs), because of cancellation
6 Likes

I also wrote this library some time ago. (Shameless self plug)

They are not zero cost bindings and it is an extra library you need to pull into your project but maybe it fits your use case :blush:. Or you just want to grab the xmlhttprequest bindings out there.

Just wanted to throw that onto the table.

5 Likes

Thank you for all the answers and clarifications.

@chenglou, this seems to be a lot of boilerplate for each developer to add to the code base…

It would be very convenient to have official ReScript bindings + examples to the standard Node + Web interfaces, ideally shipped with bs-platform. But it’s my newcomer opinion.

I would be very interested to know why Promise is discouraged in ReasonReact and React.js.

Thank you again.

2 Likes

Got it.
Thank you for the explamnation.

@mickeyvip I inlined the externals so that you can copy paste the whole thing for the playground. And yes, the boilerplate can go into a file or upstreamed into stdlib. I might look into that soon.

Taking away the bindings, the actual amount of code for a request is the same amount of line either way Except the XMLHTTPRequest solution has the advantages above. Also, you gotta count the lines from the library too if we’re doing lines count comparison. Either are easy to fit into a library.

See some of the bullet points earlier for why Promise is discouraged.

6 Likes

I’m pretty sure he made the question after reading bullet point list and considered that further explanation is needed. Also I think it is worth explaining what “we” means in this context. Is it a consensus on reason community? Is it just the rescript maintainers ? Some other group of people?

1 Like

Sorry if it’s an offtopic, but:

  1. Do you discourage bs-fetch, or Fetch in general? I mean, Fetch already has cancellation in some browsers, and it also has streaming. So: is the Fetch API totally wrong or just not ready?
  2. Is the Promise overhead really noticeable when it comes to network requests?
1 Like

If you don’t need cancellable HTTP requests, or you don’t need IE11 (or are happy to polyfill it), fetch is fine. Promises are fine. I’m not using using React, though, so that does colour my opinion on this.

I guess this explains why Js.Promise was never updated to pipe-first style. Oh well, I switched to https://github.com/aantron/promise anyway.

2 Likes