Introducing Hamo - Zero overhead hooks 🎣
Luca Gesmundo
Posted on June 19, 2019
Hi 👋
Today I would like to introduce you to a tiny library I just finished.
It's called Hamo
(Latin for hook
) and will let you hook literally everything, adding zero overhead.
The problem
Hooks are useful, but often they get in the way. You can implement them yourself with event emitters. But, any implementation would require quite a lot of effort and added complexity to your functions.
And more importantly, depending on how much critical is the function that needs to be hooked, a non negligible increase in overhead with a consequent slow down.
What if you could hook your functions, from everywhere in your app, without decreasing performance of a single ounce?
The solution
With Hamo
you can do things like the following.
const hamo = require('hamo');
const [sum, onSum] = hamo((a, b) => a + b);
onSum('after', (result, a, b) => console.log(`The sum of ${a} plus ${b} is ${result}`);
sum(1, 3);
// 3
// The sum of 1 plus 2 is 3
Pretty handy right?
You can actually hook functions before / after / oncebefore / onceafter
the hooked function.
Well, that wasn't a life changing example, let's some examples from the real world:
Node
In the following snippet we are hooking a write function.
const hamo = require('hamo');
const { writeFile } = require('hamo');
const { promisify } = require('promisify');
// The `true` argument will notify hamo that the passed function is async
const [write, onWrite, offWrite] = hamo(promisify(writeFile), true);
module.exports = {
write,
onWrite,
offWrite,
};
Then, from everywhere in your app, you can attach or detach hooks and be notified when something is written to disk.
Like a notification system for functions.
onWrite('before', path => console.log(`Writing file to ${path}`);
// Writing file to {path}
// ... somewhere `write` is busy writing something ...
offWrite('before');
// no more notifies 🌺
Browser
Maybe you want to be notified when a React
(functional) component renders.
const HelloComp = () => <h1>Hello!</h1>;
const [Hello, onHello] => hamo(HelloComp);
onHello('before', () => console.log('Rendering Hello...'));
onHello('after', () => console.log('Rendered Hello 🎉'));
// then, when mounting..
const App = () => {
console.log('Mounting App...');
return <Hello />;
};
// Mounting App...
// Rendering Hello...
// Rendered Hello 🎉
How it works?
Zero overhead
The hooks that are attached to a function are detected. Then, the body of the resulting handler is dynamically generated during runtime. There are no if
statements inside it, as only the pieces strictly needed for the currently active hooks are added to the function body.
So, zero hooks, zero overhead.
Running functions after return statements
How is it possible to run functions from a function that has already returned?
This is achieved by scheduling a micro-task
in the event-loop in the following way.
const func = () => {
Promise.resolve().then(() => console.log(42));
return 'The answer is';
};
func();
// The answer is
// 42
By running code inside an already resolved promise, you are making sure that the event-loop will pick up the tasks and will schedule them for a little later.
Well, that's pretty much it.
You can check out the repo here:
https://github.com/lucagez/hamo
Happy hooks everyone! 🎣
Posted on June 19, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.