Here’s what I did for Promise all
and allSettled
. My use case is just manually prefetching my own images in the load event so I just call blob
on the successful ones. Lemme know what you think!
//Promise.res
type t<+'a> = Js.Promise.t<'a>
exception JsError(Js.Exn.t)
external unsafeToJsExn: exn => Js.Exn.t = "%identity"
@val @scope("Promise")
external resolve: 'a => t<'a> = "resolve"
@send external then: (t<'a>, @uncurry ('a => t<'b>)) => t<'b> = "then"
type resp = {
ok: bool,
redirected: bool,
status: int,
statusText: string,
@as("type") _type: string,
url: string,
}
type outcome = {
status: string,
value: Js.Nullable.t<resp>,
reason: Js.Nullable.t<string>,
}
@val @scope("Promise")
external allSettled3: ((t<'a>, t<'b>, t<'c>)) => t<(outcome, outcome, outcome)> = "allSettled"
@val @scope("Promise")
external all3: ((t<'a>, t<'b>, t<'c>)) => t<('a, 'b, 'c)> = "all"
@send external _catch: (t<'a>, @uncurry (exn => t<'a>)) => t<'a> = "catch"
let catch = (promise, callback) => {
_catch(promise, err => {
let v = if Js.Exn.isCamlExceptionOrOpenVariant(err) {
err
} else {
JsError(unsafeToJsExn(err))
}
callback(. v)
})
}
//Prefetch.res
open Promise
type blob
@send external blob: resp => Promise.t<blob> = "blob"
@val external fetch: string => Promise.t<resp> = "fetch"
@val external fetchAllSettled: string => Promise.t<outcome> = "fetch"
let getPicsAllSettled = assets =>
{
let (asset1, asset2, asset3) = assets
allSettled3((fetchAllSettled(asset1), fetchAllSettled(asset2), fetchAllSettled(asset3)))
->then(((res1, res2, res3)) => {
[res1, res2, res3]->Js.Array2.forEach(r =>
switch r.status {
| "fulfilled" =>
switch Js.Nullable.toOption(r.value) {
| Some(resp) => Js.log2("Asset " ++ resp.url ++ " fetched ok: ", resp.ok)
| None => ()
}
| "rejected" =>
switch Js.Nullable.toOption(r.reason) {
| Some(msg) => Js.log("Asset fetch failed: " ++ msg)
| None => ()
}
| _ => ()
}
)
Ok(
[res1, res2, res3]
->Js.Array2.filter(r => r.status == "fulfilled")
->Js.Array2.map(r =>
switch Js.Nullable.toOption(r.value) {
| Some(resp) => resp->blob
| None =>
{
ok: false,
redirected: false,
status: 0,
statusText: "_",
_type: "_",
url: "_",
}->blob
}
),
)->resolve
})
->catch((. e) => {
let msg = switch e {
| JsError(err) =>
switch Js.Exn.message(err) {
| Some(msg) => msg
| None => ""
}
| _ => "Unexpected error occurred"
}
Js.log2("Fetch error: ", msg)
Error(msg)->resolve
})
}->ignore
let getPics = assets =>
{
let (asset1, asset2, asset3) = assets
all3((fetch(asset1), fetch(asset2), fetch(asset3)))
->then(((res1, res2, res3)) =>
{
let resps = [res1, res2, res3]
resps->Js.Array2.forEach(r => Js.log2("Asset " ++ r.url ++ " fetched ok: ", r.ok))
switch resps->Js.Array2.every(r => r.ok) {
| true => Ok(resps->Js.Array2.map(r => r->blob))
| false => {
let {status, statusText, url} = switch resps->Js.Array2.find(r => !r.ok) {
| Some(r) => r
| None => {
ok: false,
redirected: false,
status: 0,
statusText: "_",
_type: "_",
url: "_",
}
}
let msg = j`Fetch error for asset ${url}: $status - ${statusText}`
Js.log(msg)
Error(msg)
}
}
}->resolve
)
->catch((. e) => {
let msg = switch e {
| JsError(err) =>
switch Js.Exn.message(err) {
| Some(msg) => msg
| None => ""
}
| _ => "Unexpected error occurred"
}
Js.log2("Fetch error: ", msg)
Error(msg)->resolve
})
}->ignore
let handlerAllSettled = (assets, _e) => getPicsAllSettled(assets)
let handler = (assets, _e) => getPics(assets)
//call site
@scope("window") @val
external addWindowEventListener: (string, unit => unit) => unit = "addEventListener"
let bghand = Prefetch.handler((asset1, asset2, asset3))
//or....
let bghand = Prefetch.handlerAllSettled((asset1, asset2, asset3))
addWindowEventListener("load", bghand)