I concocted some small functions for myself to work with Belt.Result.t
and Js.Promise.t
.
Signature of functions
type promise('a) = Js.Promise.t('a);
/* Classic promise */
let then_: ('a => promise('b), promise('a)) => promise('b);
let reject: exn => promise('a);
let resolve: 'a => promise('a);
let flatMap: (promise('a), 'a => promise('b)) => promise('b);
let map: (promise('a), 'a => 'b) => promise('b);
let tap: (promise('a), 'a => unit) => promise('a);
let rejectIfNone: (promise(option('a)), exn) => promise('a);
let flatCatch: (promise('a), exn => promise('a)) => promise('a);
let catch: (promise('a), exn => 'a) => promise('a);
let finally: (promise('a), unit => unit) => promise('a);
/* Result */
type res('a, 'e) = Belt.Result.t('a, 'e);
type resPromise('a, 'e) = promise(res('a, 'e));
let resolveOk: 'a => resPromise('a, 'e);
let flatMapOk: (resPromise('a, 'e), 'a => resPromise('b, 'e)) => resPromise('b, 'e);
let mapOk: (resPromise('a, 'e), 'a => res('b, 'e)) => resPromise('b, 'e);
let flatMapError: (resPromise('a, 'e), 'e => resPromise('a, 'ee)) => resPromise('a, 'ee);
let mapError: (resPromise('a, 'e), 'e => res('a, 'ee)) => resPromise('a, 'ee);
let errorIfNone: (resPromise(option('a), 'e), 'e) => resPromise('a, 'e);
let convertResult: promise('a) => resPromise('a, [> | `exn(exn)]);
let fromResult: resPromise('a, exn) => promise('a);
Example of use:
let getJsonResponse = req =>
resolveOk(Request.bodyJSON(req))
|. errorIfNone(`InvalidRequest)
|. mapOk(body => Js.Json.decodeObject(body) |. Ok)
|. errorIfNone(`InvalidRequest)
|. mapOk(body =>
(
Utils.Json.getString("_username", body) |> Utils.def(""),
Utils.Json.getString("_password", body) |> Utils.def(""),
)
|. Ok
)
|. flatMapOk(((username, password)) => LoginClient.login(~username, ~password))
|. mapOk(token => Ok(makeJsonRes(res, Ok, Js.Dict.fromList([("token", Js.Json.string(token))]))))
|. mapError(
fun
| `InvalidRequest => Ok(makeJsonError(res, BadRequest, "Bad request"))
| `BadCredentials => Ok(makeJsonError(res, BadRequest, "Bad credentials"))
| `exn(exn) => Ok(makeJsonError(res, InternalServerError, Utils.Error.exnToString(exn))),
);
Here is another more complete example :
let submitFacebookLogin = () => {
dispatch(Security_reducer.Submit);
Facebook.WithScriptjs.loadScript(~appId=Container.facebookAppId)
|. flatMapOk(fb => Facebook.FB.login(fb, ~scope="public_profile,email") |. mapOk(res => Ok((fb, res))))
|. mapOk(((fb, res)) =>
switch (res) {
| Connected({accessToken}) => Ok((fb, accessToken))
| NotAuthorized => Error(`NotAuthorized)
| Unknown => Error(`Unknown)
}
)
|. flatMapOk(((fb, accessToken)) =>
LoginClient.facebookLogin(~accessToken)
|. flatMap(
fun
| Error(_) => {
dispatch(Security_reducer.InitialNotLogged);
Facebook.FB.Api.get(
fb,
"me?fields=id,name,email,last_name,first_name",
{"access_token": accessToken},
)
|. convertResult
|. mapOk(response =>
Ok(
dispatch(
Reductive_router_reducer.Push(
Url.signUp(
~defaultForm={
email: Utils.Json.getString("email", response),
lastname: Utils.Json.getString("last_name", response),
firstname: Utils.Json.getString("first_name", response),
},
(),
),
),
),
)
);
}
| Ok(_) =>
Apollo_client.resetStore(Container.apolloClient)
|. convertResult
|. flatMapOk(_ => getProfileId() |. convertResult)
|. mapOk(profileId => Ok(dispatch(Security_reducer.SubmitSuccess({profileId: profileId}))))
|. mapOk(_ => Ok(dispatch(Reductive_router_reducer.Redirect(Url.home, Redirect302)))),
)
)
|. mapError(error => {
let message =
switch (error) {
| `ErrorLoadFb => "Impossible de charger le script de connection facebook. Verifiez votre connexion internet."
| `NotAuthorized => "Facebook ne nous a pas autorisé a récuperer les informations nécessaires. Veuillez réessayer."
| `Unknown => "Erreur inconnue en provenance de facebook"
| `exn(exn) => Utils.Error.exnToString(exn)
};
Ok(dispatch(Security_reducer.SubmitFailed(message)));
});
};