Related posts:
- A proposal for async style sugar
- Is there an async/await, bs-let, let_ppx official solution?
- Plans for async await
Topics asking for some kind of async
/await
appear from time to time, and it’s pretty clear there’s a segment of ReScripters very interested in such syntax features.
As far as I can understand, it isn’t done or actively considered because simple to use, simple to implement, an abuse-proof solution that gives a substantial advantage over Promise.then
chains has not emerged.
To me, Promise.then
is OK until subsequent then
s require more and more computation results from the preceding clauses. This inevitably leads to highly-nested constructs which are hard to follow.
I’d like to share a thought that came to me spontaneously. If we use pattern matching to address so many problems in ReScript, could we also employ it for promises?
This is a legal ReScript code:
let main = () => {
let foo = Js.Math.random()->Js.Math.unsafe_trunc
let x = switch foo {
| 3 => 43
| 4 => 44
| _ => 45
| exception Js.Exn.Error(_) => 100
| exception Not_found => 200
}
x
}
It compiles to:
JS Code
// Generated by ReScript, PLEASE EDIT WITH CARE
'use strict';
var Js_exn = require("./stdlib/js_exn.js");
var Caml_js_exceptions = require("./stdlib/caml_js_exceptions.js");
function main(param) {
var foo = Math.trunc(Math.random());
var val;
try {
val = foo;
}
catch (raw_exn){
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
if (exn.RE_EXN_ID === Js_exn.$$Error) {
return 100;
}
if (exn.RE_EXN_ID === "Not_found") {
return 200;
}
throw exn;
}
if (val !== 3) {
if (val !== 4) {
return 45;
} else {
return 44;
}
} else {
return 43;
}
}
exports.main = main;
/* No side effect */
What if we’d introduce an await
keyword that might be used the same way as switch
:
let main = () => {
let foo = functionReturningIntPromise();
let x = await foo { // <- await instead of switch
| 3 => 43
| 4 => 44
| _ => 45
| exception Js.Exn.Error(_) => 100
| exception Not_found => 200
}
x
}
that would compile to almost the same JS with two differences:
JS Code
// Generated by ReScript, PLEASE EDIT WITH CARE
'use strict';
var Js_exn = require("./stdlib/js_exn.js");
var Caml_js_exceptions = require("./stdlib/caml_js_exceptions.js");
// DIFFERENCE
// ↓ async here
async function main(param) {
var foo = functionReturningPromise;
var val;
try {
val = await foo; // ← DIFFERENCE: await here
}
catch (raw_exn){
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
if (exn.RE_EXN_ID === Js_exn.$$Error) {
return 100;
}
if (exn.RE_EXN_ID === "Not_found") {
return 200;
}
throw exn;
}
if (val !== 3) {
if (val !== 4) {
return 45;
} else {
return 44;
}
} else {
return 43;
}
}
exports.main = main;
/* No side effect */
What it gives? Access to the earlier outputs to avoid nesting:
let fn = () => {
let foo = await readDataFromSource1() {
| data => data["result"]
}
let bar = await readDataFromSource2(foo) {
| data => data["val"]
| exception Js.Exn.Error(_) => None
}
let qux = await readDataFromSource3(foo, bar) {
| data => data
}
joinData(foo, bar, qux)
}
A mental pressure to check for error-path and use railway-oriented paradigm if one wants be closer to idiomatic FP:
let fn = () => {
let foo = await readDataFromSource1() {
| data => Ok(data["result"])
| exception _ => Error(#Source1Failed)
}
let bar = foo->Result.flatMap(foo => await readDataFromSource2(foo) {
| data => Ok(data["val"])
| exception Js.Exn.Error(_) => Error(#Source2Failed)
})
let qux = (foo, bar)->ResultX.liftM2(((foo, bar)) => await readDataFromSource3(foo, bar) {
| data => Ok(data)
| exception _ => Error(#Source3Failed)
})
(foo, bar, qux)->ResultX.liftM3(/* ... */)
}
What do you think, folks? Just a topic to discuss.