Source: util/workers/Workers.js

import { WebWorker } from './WebWorker.js';

export class Workers {

    /**
     * Gets the number of logical CPU cores.
     * @returns {Number}
     */
    static getCoreCount() {
        return window.navigator.hardwareConcurrency || 2;
    }

    /**
     * @alias Workers
     * @constructor
     * @param {*} Loader a webpack worker-loader
     * @param {Number} numWorkers Maximum number of wokers to use
     */
    constructor(Loader, numWorkers) {
        
        this.workers = [];
        this.queue = [];

        this.onWorkerSuccess = this.onWorkerSuccess.bind(this);
        this.onWorkerError = this.onWorkerError.bind(this);
        
        for (let i = 0; i < numWorkers; i++) {
            this.workers.push(new WebWorker(Loader, this.onWorkerSuccess, this.onWorkerError));
        }
    }

    /**
     * Queues some work to be proccessed by a web worker.
     * 
     * @param {*} data The data to send to the worker
     * @param {Function} cb A callback function to call with the result from the worker
     */
    process(data, cb) {
        const entry = {
            id: this.generateId(),
            processed: false,
            dataToSend: data,
            cb: cb,
        };

        this.queue.push(entry);

        this.processMoreRequests();
    }

    /**
     * Terminates all the web workers.
     */
    terminate() {
        this.workers.forEach(worker => worker.stop());
    }

    onWorkerSuccess(response, workId) {
        const err = response.err ? new Error(response.err) : null;
        this.onWorkerResponse(err, response, workId);
    }

    onWorkerError(err, workId) {
        this.onWorkerResponse(new Error(err.message, err.filename, err.lineno), null, workId);
    }

    onWorkerResponse(error, response, workId) {
        this.processMoreRequests();

        const index = this.queue.findIndex(entry => entry.id === workId);
        if (index === -1) {
            return;
        }
        const entry = this.queue[index];
        this.queue.splice(index, 1);
        if (entry.cb) {
            entry.cb(error, response);
        }
    }

    processMoreRequests() {
        if (!this.queue.length) {
            return;
        }

        const idleWorker = this.getIdleWorker();
        
        if (!idleWorker) {
            return;
        }

        const entry = this.getQueueEntry();

        if (!entry) {
            return;
        }

        idleWorker.postMessage(entry.dataToSend, entry.id);
        entry.processed = true;
    }

    getIdleWorker() {
        return this.workers.find(worker => !worker.inProgress);
    }

    getQueueEntry() {
        return this.queue.find(entry => !entry.processed);
    }

    generateId() {
        return window.crypto.getRandomValues(new Uint32Array(1))[0];
    }
}