How do I perform HTTP requests

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.

3 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?

2 Likes

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?
2 Likes

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.

3 Likes

Thank you, @spyder.
I’ll look into that library.

  1. We discourage the former, and most likely the latter until it matures.
  2. It’s about the paradigm. Promise doesn’t have cancellation and is an inextensible API. Lemme repeat that network programming is about properly handling different failure conditions (among which is proper cancellation and cleanup), not about happy path programming.

Promises are always wrong in React. See https://gist.github.com/chenglou/b6cf738a5d7adbde2ee008eb93117b49 and the first comment in the gist.

4 Likes

If you have a larger app and want to avoid boilerplate you can also look into a GraphQL client. All data-fetching ceremony is abstracted away and you just describe data requirements in a declarative way.

3 Likes

:bulb: Seems like HTTP requests over XMLHTTPRequest deserves it’s own section in the ReScript docs. Would be great to see more examples on how to do HTTP POST with body and http headers, as well as a graphql example.

13 Likes

I just wonder of how using this for multiple http request? what I got is when one request is began to load, the other is cancelled.

What I’m doing is create request on every react component’s useEffect. I guess by creating request on every component should fix, but it’s not.

React.useEffect0(() => {
let request = makeXMLHttpRequest()
request->addEventListener("load", () => {
  let response = request->response->parseResponse
  setHomepage(_ => {message: response["message"]})
})
request->addEventListener("error", () => {
  Js.log("Error logging here")
})
request->open_("GET", url.homepage)

request->send

None
})

Edited: Ok I figured out how, I just need to make external binding on every component too.

1 Like