NODEJS callback-style programming entered the JavaScript developer's toolbox a few years ago and brought with it the term 'nodeback', short for (I guess) 'node callback'. The idea of this callback is that it gets called with upto two arguments: an error value xor a success value, representing that the previous operation failed or succeeded and letting the programmer decide what to do next. For example:
fs.readFile('/etc/passwd', (err, data) => {
if (err) throw err;
console.log(data);
});
Although the nodeback style of programming has mostly been superseded in the JavaScript world, thanks to the advent of promises and async/await, developers still occasionally have to deal with it.
The problem with this callback is that either of the parameters might be undefined
, and you have to, every time, manually implement the logic of the callback in such a way that the data
is not accessed if there is a non-empty err
, and vice-versa.
In strongly, statically-typed languages like ReasonML, we have the ability to wrap up this unsafe API, with a slight runtime overhead, in a much more type-safe and ergonomic API. Here's the wrapper:
let nodeback(f) = (. err, result) =>
switch (err, result) {
| (Some(err), None) => f(Js.Result.Error(err))
| (None, Some(result)) => f(Ok(result))
// Throw if APIs break nodeback 'guarantee':
| _ => invalid_arg("Nodeback arguments invalid")
};
You can use this like so (with a hypothetical Node.Fs.readFile
binding):
Node.Fs.readFile("/etc/passwd", nodeback(fun
| Error(err) => raise({j|$err|j}) // Can't access data in this branch
| Ok(data) => Js.log(data)), // Can't access err in this branch
);
The way nodeback
works is, it takes as input a type-safe result
-handling function and converts it into a nodeback (formatted to highlight the input and output):
let nodeback:
(Js.Result.t('a, 'e) => 'b)
=>
(. option('e), option('a)) => 'b;
You can use the nodeback
wrapper to get its type-safety benefits, while passing the JavaScript side the nodeback that it expects.
[EDIT: see correction and full working example in the comment below]
Top comments (4)
Greetings, sir. I tossed this code into a file and got this error:
code i copied:
How would I fix that?
Hi Alain, somehow I completely missed your comment here. Thanks for pointing out this error. Mistake on my part, can be fixed by swapping out the
raise
withfailwith
:| Error(err) => failwith({j|$err|j}) // Can't access data in this branch
.raise
expects something of typeexn
, but we are calling it with a string. We should usefailwith
instead which does take a string.After that fix there is a second error, because the
Node.Fs.readFileSync
function doesn't actually take a nodeback. In my example I used a hypothetical functionNode.Fs.readFile
. I say hypothetical because it's not bound in the libraries shipped with BuckleScript, but of coursefs.readFile
is a real NodeJS function and you could write a binding fairly easily: nodejs.org/dist/latest-v10.x/docs/...Anyway here's a full working example:
Thanks for taking the time, sir. I can't get this to run for whatever reason. I got this gist to run, gist.github.com/idkjs/c48dda9f1dba... but when i got to apply the concept, i keep running into problems. Even searched through your book! I will keep at it. Peace to you.
My apologies, I should have tested that example more thoroughly. I've now updated my previous comment with a really working code sample. You can run it like this: