Skip to content

Instantly share code, notes, and snippets.

@aravindet
Last active June 11, 2019 07:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aravindet/930ad1f823c3d422c770177a3eb080bb to your computer and use it in GitHub Desktop.
Save aravindet/930ad1f823c3d422c770177a3eb080bb to your computer and use it in GitHub Desktop.
Fiberify: Do for any recursive algorithm what React did with React Fiber.
/*
Lets take any run-of-the-mill recursive algorithm, like factorial:
const factorial = n => (n <= 1) ? 1 : n * factorial(n - 1);
We want to break this computation up and do it during idle time,
yielding to the browser when there's high priority stuff to do like
handling events or painting the scren.
*/
// First, we wrap it in fiberify, make it an async function and add
// await to the recursive calls.
const factorial = fiberify(
async n => (n <= 1) ? 1 : n * await factorial(n - 1)
);
const fiberify = (() => {
// This is an IIFE. The actual function that goes into "fiberify" is
// returned from this function, all the way at the bottom.
// Also, it's a singleton; there just needs to be one instance for the
// whole application, for any number of fiberified functions.
// tasks is a queue of { func, args, res, rej } objects.
// func(tion) to call later
// args to pass to that func
// res(olve) is a function, to call with func's return value
// rej(ect) is a function to call if func throws an error
const tasks = [];
// Is this fiber currently running?
const running = false;
// start: Schedules a callback for when the browser is idle.
start() {
running = true;
requestIdleCallback(run);
},
// run: This is called when the browser is idle.
async run(deadline) {
while (deadline.timeRemaining() > 0 && tasks.length) {
await next();
}
running = false;
if (tasks.length) start(); // Not done yet.
},
// next: calls the next queued func, then calls its res or rej
async next() {
if (!tasks.length) return;
const { func, args, res, rej } = tasks.unshift();
try {
res(await fn(...args));
} catch (error) {
rej(error);
}
}
// push: Queues up a function to be called (along with arguments),
// and returns a promise that will resolve with that function's
// eventual return value (or reject with an error).
push(func, args) {
let res, rej;
const promise = new Promise((resolve, reject) => {
res = resolve;
rej = reject;
});
tasks.push({ func, args, res, rej });
if (!running) start();
return promise;
}
// fiberify: This is the public API function that's called
// with an async function that should be fiberified.
return function fiberify(func) {
// Returns a fiberified function, which when called
// schedules func to be called later.
return function (...args) {
return push(func, args);
};
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment