Skip to content

jiayihu/sinergia

Repository files navigation

Sinergia

npm travis

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:

  1. Long running loop: Running an expensive function (with a 2mln iterations loop) with each item of an iterable

  2. 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:

  1. Promise like es6-promise or core-js Promise. If you use babel-polyfill it's already included.

  2. requestAnimationFrame/cancelAnimationFrame. See this gist as example.

Credits

Ispiration comes largely from @LucaColonnello and @cef62.