Sinergia
sinergia is a tiny (1KB gzipped) library to run cooperative expensive tasks without blocking the UI during the computations and keeping 60fps frame rate.
Demo
A live example is available at https://jiayihu.github.io/sinergia/, with an animated box which should remain smooth at 60fps.
There are 2 examples:
-
Long running loop: Running an expensive function (with a 2mln iterations loop) with each item of an iterable
-
Long merge sort: Running a common merge sort with an array of 100k items
It's possible to play with the demo locally cloning the repo and running:
cd demo # Go to demo folder
npm install # Or `yarn install`
npm start
Installation
npm install sinergia --save
Usage
The following examples use co to consume the generator functions.
In this example work
runs a long loop for every item, but every 100000 iterations it interrupts and gives the control to sinergia
, which will resume the execution of work
when more suitable.
Execution tasks are by definition cooperative because they decide when to yield
the control of the execution.
By using yield
inside your work
you can decide the priority of the execution. Yielding often will run the task smoothly chunk by chunk but it will complete in more time. On the other hand yielding fewer times it will complete the task sooner but it will block more the main thread. Yielding zero times is equal to running the task synchronously.
import co from 'co';
import { sinergia } from 'sinergia';
function* work() {
const iterable = 'Absent gods.'.split('');
let result = '';
for (let item of iterable) {
let x = 0;
while (x < 2000000) {
x = x + 1;
// Tell sinergia when the task can be interrupted and resumed later
if (x % 100000 === 0) yield result;
}
result += item; // Simple result of task
console.log(`Result of iteration:`, result);
}
yield result; // Yield latest result
}
const execute = co(function* () {
return yield* sinergia(work);
});
execute.then((result) => {
// If the work wasn't interrupted
if (result) console.log(`Result: ${result.value}`);
});
Abort execution
Since sinergia
is just a generator, you can use the returned object to abort the execution using .return() method of generators.
The method will return { value: any }
with the value of the result computed on the latest item before aborting.
import co from 'co';
import { sinergia } from 'sinergia';
function* work() {
const iterable = 'Absent gods.'.split('');
let result = '';
for (let item of iterable) {
let x = 0;
while (x < 2000000) {
x = x + 1;
// Tell sinergia when the task can be interrupted and resumed later
if (x % 100000 === 0) yield result;
}
result += item; // Simple result of task
console.log(`Result of iteration:`, result);
}
yield result; // Yield latest result
}
let iterator;
const execute = co(function* () {
iterator = sinergia(work);
return yield* iterator;
});
execute.then((result) => {
// If the work wasn't interrupted
if (result) console.log(`Result: ${result.value}`);
});
window.setTimeout(() => {
const result = iterator.return();
console.log('Interrupted result', result.value);
}, 5000);
API
sinergia(work: GeneratorFunction): Generator
It runs asynchronously the work
function in not blocking way.
Returns the Generator object.
Browser support
sinergia requires polyfills for:
-
Promise like es6-promise or core-js Promise. If you use babel-polyfill it's already included.
-
requestAnimationFrame/cancelAnimationFrame. See this gist as example.
Credits
Ispiration comes largely from @LucaColonnello and @cef62.