2.0.0 • Published 5 years ago

inline-webworker-functional v2.0.0

Weekly downloads
12
License
ISC
Repository
github
Last release
5 years ago

inline-webworker-functional

Execute your time consuming JS functions asynchronously in the background, works with both NodeJS and browser environment. When in browser, it creates inline web worker under the hood; When in NodeJS, it creates child process under the hood.

Motivation

Call your functions asynchronously, without worrying anything about the underlying web worker API or child process API at all, like creating separate JS file, message passing etc.

Installation

NodeJS Environment

install from NPM

npm install inline-webworker-functional

import to your JS file

const {makeSimpleWorker, makeStatefulWorker, terminateStatefulWorker} = require("inline-webworker-functional/node")

Webpack Environment

install from NPM

npm install inline-webworker-functional

import to your JS file

const {makeSimpleWorker, makeStatefulWorker, terminateStatefulWorker} = require("inline-webworker-functional/browser")

Use Directly in Browser

<script src="https://unpkg.com/inline-webworker-functional/browserlib.js"></script>

Usage

Basic usage

Make a function asynchronous:

function lotsOfWork(x, y) {
    let s = 0;
    for (let i = 0; i < x; ++i) {
        for (let j = 1; j < y; ++j) {
            s += i / j;
        }
    }
    return s;
}

(async()=>{
    // create a asynchronous version of your function
    const lotsOfWorkAsync = makeSimpleWorker(lotsOfWork);
    
    // execute your function in the background
    const result = await lotsOfWorkAsync(1e4, 1e4);
    console.log(result); 
})();

Due the to the limitation of web worker, the job function (the function to convert) must be completely self-contained, without any reference to external variables/functions/classes. If your job function requires other helper functions/classes as subroutines, please inline those dependencies into your function, like this:

function complexWork(x) {
    class Circle {
        constructor(r) {
            this.r = r;
        }
        getArea() {
            return Math.PI * this.r * this.r;
        }
    }
    return new Circle(x).getArea();
}
(async()=>{
    const complexWorkAsync = makeSimpleWorker(complexWork);
    const result = await complexWorkAsync(2);
	console.log(result); // prints 12.566370614359172
})();

And also, the worker task function cannot take parameters of arbitrary type. The parameters must be compatible with the structured clone algorithm.

Advanced usage: keeping internal state

the async function returned by makeSimpleWorker does the following things: When invoked, this async function creates a worker, executes it, and then destroys it. It works well in most cases. But in some scenarios, you may want more fine controls, like:

  • you want to keep some internal states in your worker
  • you simply don't want your worker to be automatically destroyed

In these cases, you will need to call makeStatefulWorker instead.

First, you need to have a worker task factory function that returns a dictionary of worker tasks.

function createStatefulWorkerTasks() {

    let balance = 0;

    function save(x) {
        balance += x;
        return balance;
    }

    function withdraw(x) {
        if(balance < x) throw new Error("insufficient balance");
        balance -= x;
        return balance;
    }

    return {save, withdraw};
}

Then, call makeStatefulWorker on your factory function. It asynchronously returns a worker object.

(async()=>{
    const worker = await makeStatefulWorker(createStatefulWorkerTasks);
})();

Now the worker object contains save and withdraw attribute, those are asynchronous versions of the corresponding worker tasks.

(async()=>{
    const worker = await makeStatefulWorker(createStatefulWorkerTasks);
    console.log(await worker.save(200)); // prints 200
    console.log(await worker.withdraw(300)); // throws an error with message "insufficient balance"
})();

A stateful worker doesn't automatically destroy itself. You need to manually destroy it by calling terminateStatefulWorker

terminateStatefulWorker(worker) // where "worker" is the object return by "makeStatefulWorker"

Advanced usage: invoking host functions from worker

So far, the worker is passively communicating with the host. In other words, the host need to first request something to trigger the workers response. But in some scenarios, the worker need to actively communicate with the host, even though the host is not invoking any worker task at all. For example, this is what you want:

function foo(msg) {
    console.log('worker says:', msg);
}

function workerTask() {
    setInterval(()=>{
        foo("hello");
    }, 500);
    return {};
}

(async ()=>{
    const worker = await makeStatefulWorker(workerTask);
})();

However, when you execute this, you will get an error because foo is a function defined only in the host but not in the worker. You need to explicit specify that you want the worker to be able to trigger foo function on the host, by passing foo to makeStatefulWorker in the second parameter. Here is how you can do it:

function foo(msg) {
    console.log('worker says:', msg);
}

function workerTask() {
    setInterval(()=>{
        foo("hello");
    }, 500);
    return {};
}

(async ()=>{
    const worker = await makeStatefulWorker(workerTask, {foo});
})();

In this way the worker can trigger the foo function on the host. However there are certain limitations on the functions to trigger:

  • it's parameters must be compatible with structured clone algorithm
  • it must not have any return value

Advanced usage: importing libraries in worker

Browser and Webpack environment

In browser or Webpack environment, there is currently not supported. The worker must work entirely on its own, without importing any libraries.

NodeJS environment

In NodeJS environment, the worker can import other libraries. But you have to put the import statement within the worker task function. However, the import keyword refuses to appear within function body, it must sits at the top of the JS/TS file. But that's fixable, you can use the old require semantics instead, like this:

function workerTask() {
    const fs = require("fs");
    
    // fs library can be used in worker now
}

makeSimpleWorker(workerTask);
2.0.0

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago