Last active
June 11, 2019 07:17
-
-
Save aravindet/930ad1f823c3d422c770177a3eb080bb to your computer and use it in GitHub Desktop.
Fiberify: Do for any recursive algorithm what React did with React Fiber.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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