Skip to content

WillNewmarch/bifur

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bifur

Bifur

Multi-threading for the masses!

Github License GitHub release

Accessible and understandable multi-threading that doesn’t complicate your codebase.

Not only is Bifur a kick-arse dwarf from The Hobbit. His name literally means to divide into two branches or parts.

Bifur allows you to run code alongside other code so as not to block the main thread (i.e. cause the browser to lock up) when performing heavy operations!

There are two big hurdles when running code in parallel with web workers:

  • Web Workers are fiddly and complicated to set up initially.
  • Knowing when a particular request has returned from a Web Worker can be tricky.

Bifur does the hard work for you and allows you to pass in a function and an array of arguments, and returns you a promise that will resolve when your function is complete!

Table of Contents

Compatibility

Bifur is intended for browser use only and is compatible with all major browsers.

Check for specific browser compatibility here: https://caniuse.com/webworkers

Installation

Use the package manager npm to install Bifur.

npm install bifur

Usage

Firstly, import Bifur into your environment...

import Bifur from "bifur";

If you want to import for TS...

import Bifur from "bifur/src";

And if you want to import a bundled version...

import Bifur from "bifur/bundle";

Additionally, you can import directly into HTML files via something like unpkg:

<script type="module">
    import 'https://unpkg.com/bifur';
</script>

From here on you can call Bifur.run which accepts two arguments, a Function and an Array respectively.

Bifur.run(
    (a,b) => {
        return a + b;
    },
    [2,3]
);

The function should be self-contained as it will be run in a completely separate thread to your current code.

The array of arguments should align with the parameters that your function expects.

The run method will then return a Promise that resolves when the function has finished its calculations.

Bifur.run(
    (a,b) => {
        return a + b;
    },
    [2,3]
).then(result => {
    console.log(result); // 5
})

OR

const result = await Bifur.run(
    (a,b) => {
        return a + b;
    },
    [2,3]
);
// result = 5

Documentation

Bifur can be used statically for ease and simplicity.

Bifur currently provides just one method, run.

Methods

run

Bifur.run(func, array);

Runs the provided function asynchronously to the main thread, passing it the arguments supplied via the array provided.

Arguments

[method] (Function): The method to be involved asynchronously to the main thread.

[arguments] (Array): The arguments to pass to the array. Note: this array will be spread to allow the function to have multiple parameters.

Returns

(any): Returns the returned value of the function provided.

Example:

const result = await Bifur.run(
    (a,b) => {
        return a + b;
    },
    [2,3]
);
// result = 5

persist

const persistedThread = Bifur.persist(func);

Creates a persisted worker that performs the supplied function when run. The advantage of this over simply using the run method is that the worker persists and isn't destroyed after one use. This helps performance, but also allows you to persist a state within the thread.

Arguments

[method] (Function): The method to be involved asynchronously to the main thread.

Returns

(any): Returns an instance of PersistedWrapper.

Example 1:

const thread = Bifur.persist(
    (a,b) => {
        return a + b;
    }
);
const result = await thread.run([2,3]);
// result = 5

Example 2 - state persistence:

const thread = Bifur.persist(
    (a,b) => {
        // Create a state in the thread if one doesn't exist already.
        if(!this.state) {
            this.state = 0;
        }
        const result = a + b;
        this.state += result;
        return this.state;
    }
);
const result = await thread.run([2,3]);
// result = 5

const result = await thread.run([2,3]);
// result = 10

Additional Information

Heavier Examples

The following functionality, if run in browser, will lock up the UI for several seconds while computing. Paste it into the browser console to see for yourself.

let fibonacci = (num) => { 
    if (num <= 1) return 1; 
    return fibonacci(num - 1) + fibonacci(num - 2); 
}
console.time('fibonacci');
fibonacci(42);
console.timeEnd('fibonacci');

When running this with Bifur, the UI will continue running as usual while this calculation is made on a separate thread! Implemented like so...

const result = await Bifur.run(
    (num) => {
        let fibonacci = (num) => {
            if (num <= 1) return 1;
            return fibonacci(num - 1) + fibonacci(num - 2);
        }
        return fibonacci(num);
    },
    [42]
);

Now running the same function but using a persisted worker and holding the results in state means we can recall the result of a previously calculated function and respond far more quickly.

const thread = Bifur.persist((num) => {
    // Generate a state if one doesn't exist.    
    if(!this.state) {
        this.state = {};
    }

    // If we've already worked this out and saved it in the state then return the previous result.
    if(!!this.state[num]) {
        return this.state[num];
    }

    let fibonacci = (num) => {
        if (num <= 1) return 1;
        return fibonacci(num - 1) + fibonacci(num - 2);
    }
    const result = fibonacci(num);

    // Persist the result in the thread's state.
    this.state[num] = result;

    return result;
});

await thread.run([42]); // Runs in a few seconds

await thread.run([42]); // Runs in a few milliseconds

Note that pasting this into your browser will not work unless you import Bifur!

Why not just use Promises?

To quote an excellent answer from our beloved StackOverflow:

Deferred/Promises and Web Workers address different needs:

- Deferred/promise are constructs to assign a reference to a result not yet available, and to organize code that runs once the result becomes available or a failure is returned.

- Web Workers perform actual work asynchronously (using operating system threads not processes - so they are relatively light weight).

Source: https://stackoverflow.com/questions/20929508/web-workers-vs-promises#answers-header

Future Improvements

  • Test files could be TypeScript.
  • Worker could be mocked in more detail.
  • Tests could be run in a headless browser environment like Selenium.

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Please make sure to update tests as appropriate.

License

MIT