ReScriptでfetchを実行する

# はじめに
JavaScriptでは以下のように書くとfetchが実行できます。
```JavaScript:fetch_test.js
async function fetchText() {
  const response = await fetch("https://qiita.com/");
  const text = await response.text();
  console.log(text);
}
fetchText();
```
同じことをReScriptでやるにはどうするのか、試行錯誤してみました。
# 環境
* macOS Sonoma(バージョン 14.4.1)
* ReScript 11.1.0
* @rescript/core 1.3.0
# 最も簡単な方法
~~~ReScript:fetch_test01.res
%%raw(`
async function fetchText() {
  const response = await fetch("https://qiita.com/");
  const text = await response.text();
  console.log(text);
}
fetchText();
`)
~~~
JavaScriptプログラム全体を``%%raw(`......`)``で囲めば、ReScriptプログラムとして処理されます。
# トップレベルではなく式のレベルで`raw`を用いる
ReScriptで書かれた部分を増やしていきます。まず`fetchText()`の呼び出し部分をReScript化をがんばります。
~~~ReScript:NG : fetch_test02.res
%%raw(`
async function fetchText() {
  const response = await fetch("https://qiita.com/");
  const text = await response.text();
  console.log(text);
}
`)
fetchText()
~~~
`fetchText()`の呼び出し部分で、`The value fetchText can't be found`が表示されエラーになります。
***
関数部分だけ`%raw`で囲み、エラーを解消します。
~~~ReScript:fetch_test02.res
let fetchText = %raw(`async function fetchText() {
  const response = await fetch("https://qiita.com/");
  const text = await response.text();
  console.log(text);
}`)
fetchText()
~~~
これで動作します。
***
さらにReScriptの部分を増やすため、各文を`%raw`で囲ってみます。
~~~ReScript:NG fetch_test03.res
let fetchText = async () => {
  let response = %raw(`await fetch("https://qiita.com/");`)
  let text = %raw(`await response.text();`)
  %raw(`console.log(text);`)
}
fetchText()
~~~
色々なエラーが出てうまくいきません。
`fetchText()`のところで、``This function call is at the top level and is expected to return `unit`. But it's returning `promise<'a>`.``が出るので、ここだけ直します。
戻り値を明示的に無視するために`ignore`関数を用います。
~~~ReScript:NG fetch_test03.res
let fetchText = async () => {
  let response = %raw(`await fetch("https://qiita.com/");`)
  let text = %raw(`await response.text();`)
  %raw(`console.log(text);`)
}
ignore(fetchText())
~~~
`fetchText()`の部分のエラーだけ解消しました。
# `external`を書く
ReScriptからJavaScriptの関数を呼び出すときは、`external`文を書きます。
[External (Bind to Any JS Library)
](https://rescript-lang.org/docs/manual/latest/external)によると`=`の左辺にReScriptでの関数名と型を書いて、右辺にJavaScripでの名前を書けば良いようです。
`fetch`の型は[fetch() global]( functionhttps://developer.mozilla.org/en-US/docs/Web/API/fetch)を見ながら考えると``string => promise<`a>``になるでしょう。[^1]
[^1]:このエントリでは`fetch(resource)`を使うので、こうなると思います。`fetch(resource, options)`を用いるときは``(string, `a) => promise<`a>``になるでしょう。

ということで、`fetch`の`external文は`
```ReScript:external fetch
external fetch: string => promise<'a> = "fetch"
```
とします。
`response.text()`の`text`の型は[Response: text() method](https://developer.mozilla.org/en-US/docs/Web/API/Response/text)を見ながら考えると`() => promise<string>`なので
```ReScript:external text
external text: () => promise<string> = "text"
```
とします。プログラム全体は
```ReScript:NG fetch_test03.res
external fetch: string => promise<'a> = "fetch"
external text: () => promise<string> = "text"
let fetchText = async () => {
  let response = await fetch("https://qiita.com/")
  let text = await text(response)
  Console.log(text)
}
ignore(fetchText())
```
となります。ビルドは成功しますが、実行すると失敗します。
[Function](https://rescript-lang.org/docs/manual/latest/bind-to-js-function#object-method)を見ると、`text`はオブジェクトメソッドなので書き方が違うようです。
`external`の前に`@send`を付け、第一引数にオブジェクトをもたせます。
書き直して、
```ReScript:fetch_test03.res
external fetch: string => promise<'a> = "fetch"
@send external text: ('a, ()) => promise<string> = "text"
let fetchText = async () => {
  let response = await fetch("https://qiita.com/")
  let text = await text(response, ())
  Console.log(text)
}
ignore(fetchText())
```
で、動作するようになりました。
# 動くReScriptプログラムに手をいれていく
動くから、これでいいじゃないかと思います。
でも気になるところもあるので手を入れていきます。
まず、`text`の引数は`()`のみです。この場合は、引数の表記が省略できるようです。
省略して書き直すと、
```ReScript:fetch_test04.res
external fetch: string => promise<'a> = "fetch"
@send external text: 'a => promise<string> = "text"
let fetchText = async () => {
  let response = await fetch("https://qiita.com/")
  let text = await text(response)
  Console.log(text)
}
ignore(fetchText())
```
で、動作します。
***
`fetch`の戻り値を``promise<`a>``と広めにとっているので、例えば、
```ReScript:NG fetch_test05.res
external fetch: string => promise<'a> = "fetch"
@send external text: ('a) => promise<string> = "text"
let fetchText = async () => {
  let res = await fetch("https://qiita.com/")
  let txt = await text(res)
  Console.log(txt)
  let txt2 = await text("This is a text.")
  Console.log(txt2)
}
ignore(fetchText())
```
としても、ビルドは成功します。でも実行は失敗します。
これを防ぐために`fetch`の戻り値を`response`として規定します。
```ReScript:NG fetch_test05.res
type response
external fetch: string => promise<response> = "fetch"
@send external text: response => promise<string> = "text"
let fetchText = async () => {
  let res = await fetch("https://qiita.com/")
  let txt = await text(res)
  Console.log(txt)
  let txt2 = await text("This is a text.")
  Console.log(txt2)
}
ignore(fetchText())
```
これでビルド時にチェックがかかるようになりました。
`txt2`の部分を削除して
```ReScript:fetch_test05.res
type response
external fetch: string => promise<response> = "fetch"
@send external text: response => promise<string> = "text"
let fetchText = async () => {
  let res = await fetch("https://qiita.com/")
  let txt = await text(res)
  Console.log(txt)
}
ignore(fetchText())
```
とすると動作します。
***
[Bind to Global JS Values](https://rescript-lang.org/docs/manual/latest/bind-to-global-js-values)や、色々なサンプルを見ると、`fetch`は`global scope`にいるので`@val`を付けるようです。
```ReScript:fetch_test06.res
type response
@val external fetch: string => promise<response> = "fetch"
@send external text: response => promise<string> = "text"
let fetchText = async () => {
  let res = await fetch("https://qiita.com/")
  let txt = await text(res)
  Console.log(txt)
}
ignore(fetchText())
```
***
これで完成で問題ないんですが、[Pipe](https://rescript-lang.org/docs/manual/latest/pipe)を使って、
```ReScript:fetch_test.res
type response
@val external fetch: string => promise<response> = "fetch"
@send external text: response => promise<string> = "text"
let fetchText = async () => {
  let res = await fetch("https://qiita.com/")
  let txt = await res->text
  Console.log(txt)
}
fetchText()->ignore
```
を完成版とします。