├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── dist ├── evalworker.min.js ├── threadpool.js └── threadpool.min.js ├── gulpfile.js ├── lib ├── ThreadPool │ ├── Job.js │ ├── Thread.js │ ├── ThreadPool.js │ ├── WorkerFactory.js │ └── WorkerWrapper.js ├── evalworker.js ├── genericWorker.js └── index.js ├── package.json ├── samples ├── count.html ├── error.html ├── index.html └── style.css ├── spec ├── helpers.js └── threadpool.spec.js └── src ├── ThreadPool ├── Job.js ├── Thread.js ├── ThreadPool.js ├── WorkerFactory.js └── WorkerWrapper.js ├── evalworker.js ├── genericWorker.js └── index.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures" : { 3 | "arrowFunctions" : true, 4 | "blockBindings" : true, 5 | "classes" : true, 6 | "defaultParams" : true, 7 | "destructuring" : true, 8 | "jsx" : true, 9 | "modules" : true, 10 | "restParams" : true 11 | }, 12 | "env" : { 13 | "browser" : true, 14 | "node" : true 15 | }, 16 | "globals" : { 17 | "Blob" : true, 18 | "BlobBuilder" : true 19 | }, 20 | "rules" : { 21 | "global-strict" : 0, 22 | "key-spacing" : [0, { "beforeColon": false, "afterColon": true }], 23 | "no-multi-spaces" : 0, 24 | "quotes" : [1, "single", "avoid-escape"] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .publish 3 | bower_components 4 | node_modules 5 | public 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "0.12" 5 | - "4.1" 6 | 7 | # Here comes the stuff to make webworker-threads work: 8 | 9 | install: 10 | - CXX="g++-4.8" CC="gcc-4.8" npm install 11 | 12 | # thanks to http://stackoverflow.com/a/30925448/1283667: 13 | addons: 14 | apt: 15 | sources: 16 | - ubuntu-toolchain-r-test 17 | packages: 18 | - gcc-4.8 19 | - g++-4.8 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Andy Wermke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## threadpool.js [![Build Status](https://travis-ci.org/andywer/threadpool-js.svg?branch=master)](https://travis-ci.org/andywer/threadpool-js) [![npm version](https://badge.fury.io/js/threadpool-js.svg)](http://badge.fury.io/js/threadpool-js) [![Bower version](https://badge.fury.io/bo/threadpool-js.svg)](http://badge.fury.io/bo/threadpool-js) 2 | 3 | __Deprecation notice: This package is near its end of life. Switch to [threads.js](https://github.com/andywer/threads.js) instead. It provides the same features plus additional ones and is generally more awesome :)__ 4 | __PS: If you feel different about it, feel free to open an issue.__ 5 | 6 | _threadpool.js_ is aimed to be a general-purpose multi-threading library for Javascript. 7 | Its key features are *portability* and *ease of use*. The library can either be used in a stand-alone fashion or as a *[require.js](http://requirejs.org/)* module. 8 | 9 | ## Usage 10 | 11 | You can add threadpool-js to your project using npm or bower: 12 | 13 | ```bash 14 | npm install --save threadpool-js 15 | # or 16 | bower install --save threadpool-js 17 | ``` 18 | 19 | Or just by adding this script tag: 20 | 21 | ```html 22 | 23 | ``` 24 | 25 | ## Example use 26 | 27 | Include the library at first. Just add the *threadpool.js* file to your project and include it per ` 6 | 7 | 8 | 9 | 10 |

Counting asynchronously from 1 to 19...

11 |

12 | 13 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /samples/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Error handling 5 | 6 | 7 | 8 | 18 | 19 | 20 |

21 | 22 |

Code:

23 |
24 | var workerFunction = function (param, done) {
25 |     // divide by undeclared variable `y`:
26 |     var x = param / y;
27 | };
28 | 
29 | var pool = new ThreadPool();
30 | 
31 | pool.run(workerFunction, 10)
32 | .done(function (result) {
33 |     $('#error').text("No error.");
34 | })
35 | .error(function (errorEvent) {
36 |     $('#error').append(
37 |         $('').text("Worker encountered a problem:"),
38 |         $('').text(errorEvent.message)
39 |     )
40 | });
41 | 42 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /samples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Thread pool samples 5 | 6 | 12 | 13 | 14 |

threadpool-js samples

15 | 16 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /samples/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 12pt Arial, sans-serif; 3 | margin: 4% 6%; 4 | } 5 | 6 | a { 7 | color: blue; 8 | text-decoration: none; 9 | } 10 | 11 | a:hover { 12 | color: red; 13 | } 14 | 15 | h1 { 16 | margin: 30px 0; 17 | } 18 | 19 | #source-title { 20 | margin-top: 40px; 21 | } 22 | 23 | #source { 24 | padding: 6px 10px; 25 | background: #f0f0f0; 26 | } 27 | -------------------------------------------------------------------------------- /spec/helpers.js: -------------------------------------------------------------------------------- 1 | 2 | // Hack to make the library work with `webworker-threads` 3 | // (since we can simply pass a function to the Worker constructor, but don't have blob building logic) 4 | 5 | function FakeBlob (codeStrings) { 6 | this.codeString = codeStrings[0]; 7 | } 8 | 9 | function fakeCreateBlobURL (fakeBlob) { 10 | return new Function(fakeBlob.codeString); 11 | } 12 | 13 | var window = { 14 | Blob: FakeBlob, 15 | createBlobURL: fakeCreateBlobURL, 16 | navigator: { 17 | userAgent: 'node.js' 18 | } 19 | }; 20 | 21 | // neccessary, because `require('../src/threadpool')` already needs the Worker class 22 | global.Worker = require('webworker-threads').Worker; 23 | global.window = window; 24 | global.Blob = FakeBlob; 25 | global.createBlobURL = fakeCreateBlobURL; 26 | 27 | module.exports = { 28 | ThreadPool: require('../lib'), 29 | Worker: global.Worker, 30 | window: window 31 | }; 32 | -------------------------------------------------------------------------------- /spec/threadpool.spec.js: -------------------------------------------------------------------------------- 1 | require('./helpers'); 2 | require('../dist/threadpool.js'); 3 | 4 | var ThreadPool = window.ThreadPool; 5 | var expect = require('expect.js'); 6 | 7 | 8 | /** 9 | * Wait until condition is fulfilled or max waiting time has passed. 10 | * @param {Function} conditionCb 11 | * @param {Number} maxWaitMs 12 | * @param {Function} fulfillCb 13 | */ 14 | function waitFor(conditionCb, maxWaitMs, fulfillCb) { 15 | function repeatingCheck() { 16 | if (conditionCb()) { 17 | clearInterval(checkInterval); 18 | clearTimeout(timeout); 19 | fulfillCb(true); 20 | } 21 | } 22 | 23 | function cancel() { 24 | clearInterval(checkInterval); 25 | fulfillCb(false); 26 | } 27 | 28 | var checkEveryMs = 10; 29 | var checkInterval = setInterval(repeatingCheck, checkEveryMs); 30 | var timeout = setTimeout(cancel, maxWaitMs); 31 | } 32 | 33 | 34 | describe('threadpool', function() { 35 | 36 | it('has been initialized', function() { 37 | expect(ThreadPool).to.be.a('function'); 38 | }); 39 | 40 | describe('workers', function() { 41 | 42 | it('are spawned', function(done) { 43 | var pool = new ThreadPool(); 44 | var actionCalled = 0; 45 | 46 | var action = function(param, actionDone) { 47 | actionDone(); 48 | }; 49 | 50 | pool.run(action); 51 | pool.run(action); 52 | 53 | pool.done(function() { 54 | actionCalled++; 55 | }); 56 | 57 | waitFor(function() { return actionCalled === 2; }, 500, function() { 58 | expect(actionCalled).to.equal(2); 59 | done(); 60 | }); 61 | }); 62 | 63 | it('trigger start event', function(done) { 64 | var pool = new ThreadPool(); 65 | var startEvents = 0; 66 | var doneCalled = false; 67 | 68 | var action = function(param, actionDone) { 69 | actionDone(); 70 | }; 71 | 72 | function startEventHandler() { 73 | startEvents++; 74 | } 75 | 76 | pool.run(action).start(startEventHandler); 77 | pool.run(action).start(startEventHandler); 78 | 79 | pool.done(function() { 80 | expect(startEvents).to.equal(2); 81 | if (doneCalled) { return; } 82 | 83 | doneCalled = true; 84 | done(); 85 | }); 86 | }); 87 | 88 | it('trigger error event', function(done) { 89 | var pool = new ThreadPool(); 90 | var errorEvents = 0; 91 | var poolError = 0; 92 | 93 | var action = function() { 94 | throw new Error('Test'); 95 | }; 96 | 97 | function errorEventHandler() { 98 | errorEvents++; 99 | } 100 | 101 | pool.run(action).error(errorEventHandler); 102 | pool.run(action).error(errorEventHandler); 103 | 104 | pool.error(function() { 105 | poolError++; 106 | 107 | expect(errorEvents).to.equal(poolError); 108 | if (errorEvents == 2) { done(); } 109 | }); 110 | }); 111 | 112 | it('can work asynchronously', function(done) { 113 | var pool = new ThreadPool(); 114 | var actionCalled = 0; 115 | 116 | var action = function(param, actionDone) { 117 | thread.nextTick(actionDone); 118 | }; 119 | 120 | pool.run(action); 121 | pool.run(action); 122 | 123 | pool.done(function() { 124 | actionCalled++; 125 | }); 126 | 127 | waitFor(function() { return actionCalled === 2; }, 500, function() { 128 | expect(actionCalled).to.equal(2); 129 | done(); 130 | }); 131 | }); 132 | 133 | it('can be re-used', function(done) { 134 | // only two threads 135 | var pool = new ThreadPool(2); 136 | var actionCalled = 0; 137 | 138 | var action = function(param, actionDone) { 139 | thread.nextTick(function() { 140 | actionDone(); 141 | }); 142 | }; 143 | 144 | // queueing more jobs than there are threads 145 | pool.run(action); 146 | pool.run(action); 147 | pool.run(action); 148 | 149 | pool.done(function() { 150 | actionCalled++; 151 | }); 152 | 153 | // TODO: Spy on WorkerFactory to make sure there are only two threads spawned 154 | 155 | pool.allDone(function() { 156 | try { 157 | expect(actionCalled).to.equal(3); 158 | } catch (error) { 159 | return done(error); 160 | } 161 | done(); 162 | }); 163 | }); 164 | 165 | }); 166 | 167 | 168 | it('allDone() is triggered once on overall completion', function(done) { 169 | var pool = new ThreadPool(); 170 | var actionCalled = 0; 171 | var allDoneCalled = 0; 172 | 173 | var action = function(param, actionDone) { 174 | actionDone(); 175 | }; 176 | 177 | pool.run(action); 178 | pool.run(action); 179 | 180 | pool.done(function() { 181 | actionCalled++; 182 | }); 183 | 184 | pool.allDone(function() { 185 | allDoneCalled++; 186 | }); 187 | 188 | waitFor(function() { return allDoneCalled === 1; }, 500, function() { 189 | expect(allDoneCalled).to.equal(1); 190 | done(); 191 | }); 192 | }); 193 | 194 | }); 195 | -------------------------------------------------------------------------------- /src/ThreadPool/Job.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import EventEmitter from 'eventemitter3'; 4 | 5 | function arrayEquals(a, b) { 6 | return !(a < b || a > b); 7 | } 8 | 9 | export default class Job extends EventEmitter { 10 | 11 | /** 12 | * @param {String} script Script filename or function. 13 | * @param {Object|Array} [param] Optional. Parameter (or array of parameters) to be passed to the thread or false/undefined. 14 | * @param {Object[]} [transferBuffers] Optional. Array of buffers to be transferred to the worker context. 15 | */ 16 | constructor(script, param, transferBuffers) { 17 | super(); 18 | 19 | this.param = param; 20 | this.transferBuffers = transferBuffers; 21 | this.importScripts = []; 22 | 23 | if (typeof script === 'function') { 24 | var funcStr = script.toString(); 25 | this.scriptArgs = funcStr.substring(funcStr.indexOf('(') + 1, funcStr.indexOf(')')).split(','); 26 | this.scriptBody = funcStr.substring(funcStr.indexOf('{') + 1, funcStr.lastIndexOf('}')); 27 | this.scriptFile = null; 28 | } else { 29 | this.scriptArgs = null; 30 | this.scriptBody = null; 31 | this.scriptFile = script; 32 | } 33 | } 34 | 35 | getParameter() { 36 | return this.param; 37 | } 38 | 39 | getImportScripts() { 40 | return this.importScripts; 41 | } 42 | 43 | setImportScripts(scripts) { 44 | this.importScripts = scripts; 45 | } 46 | 47 | getBuffersToTransfer() { 48 | return this.transferBuffers; 49 | } 50 | 51 | /** 52 | * @return {Object} Object: { args: ["argument name", ...], body: "" } 53 | * Usage: var f = Function.apply(null, args.concat(body)); 54 | * (`Function.apply()` replaces `new Function()`) 55 | */ 56 | getFunction() { 57 | if (!this.scriptArgs) { 58 | return null; 59 | } 60 | 61 | return { 62 | args: this.scriptArgs, 63 | body: this.scriptBody 64 | }; 65 | } 66 | 67 | getScriptFile() { 68 | return this.scriptFile; 69 | } 70 | 71 | /// @return True if `otherJob` uses the same function / same script as this job. 72 | functionallyEquals(otherJob) { 73 | return otherJob && 74 | (otherJob instanceof Job) && 75 | arrayEquals(otherJob.scriptArgs, this.scriptArgs) && 76 | otherJob.body === this.body && 77 | otherJob.scriptFile === this.scriptFile; 78 | } 79 | 80 | /** 81 | * Adds a callback function that is called when the job is about to start. 82 | * @param {Function} callback 83 | * function(result). `result` is the result value/object returned by the thread. 84 | */ 85 | start(callback) { 86 | return this.on('start', callback); 87 | } 88 | 89 | /** 90 | * Adds a callback function that is called when the job has been (successfully) finished. 91 | * @param {Function} callback 92 | * function(result). `result` is the result value/object returned by the thread. 93 | */ 94 | done(callback) { 95 | return this.on('done', callback); 96 | } 97 | 98 | /** 99 | * Adds a callback function that is called if the job fails. 100 | * @param {Function} callback 101 | * function(error). `error` is an instance of `Error`. 102 | */ 103 | error(callback) { 104 | return this.on('error', callback); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/ThreadPool/Thread.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import EventEmitter from 'eventemitter3'; 4 | import WorkerFactory from './WorkerFactory'; 5 | 6 | export default class Thread extends EventEmitter { 7 | 8 | constructor(threadPool) { 9 | super(); 10 | 11 | this.threadPool = threadPool; 12 | this.factory = new WorkerFactory({ evalWorkerUrl : threadPool.evalWorkerUrl }); 13 | 14 | this.worker = null; 15 | this.currentJob = null; 16 | this.lastJob = null; 17 | } 18 | 19 | terminate() { 20 | if (this.worker) { 21 | this.worker.terminate(); 22 | this.worker = null; 23 | } 24 | } 25 | 26 | run(job) { 27 | var needToInitWorker = true; 28 | var transferBuffers = job.getBuffersToTransfer() || []; 29 | 30 | this.currentJob = job; 31 | this.factory.once('new', worker => { this.wireEventListeners(worker, job); }); 32 | 33 | if (this.worker) { 34 | if (this.lastJob && this.lastJob.functionallyEquals(job)) { 35 | needToInitWorker = false; 36 | } else { 37 | this.worker.terminate(); 38 | this.worker = null; 39 | } 40 | } 41 | 42 | job.emit('start'); 43 | 44 | try { 45 | if (needToInitWorker) { 46 | if (job.getScriptFile()) { 47 | this.worker = this.factory.runScriptFile(job.getScriptFile(), job.getParameter(), transferBuffers); 48 | } else { 49 | this.worker = this.factory.runCode(job.getFunction(), job.getParameter(), job.getImportScripts(), transferBuffers); 50 | } 51 | } else { 52 | this.wireEventListeners(this.worker, job, true); 53 | 54 | if (job.getScriptFile()) { 55 | this.factory.passParamsToWorkerScript(this.worker, job.getParameter(), transferBuffers); 56 | } else { 57 | this.factory.passParamsToGenericWorker(this.worker, job.getFunction(), job.getParameter(), job.getImportScripts(), transferBuffers); 58 | } 59 | } 60 | } finally { 61 | // always remove all listeners (for this job), so they cannot be triggered when this function is later 62 | // called with a different job 63 | this.factory.removeAllListeners('new'); 64 | } 65 | } 66 | 67 | wireEventListeners(worker, job, removeExisting = false) { 68 | if (removeExisting) { 69 | worker.removeAllListeners('message'); 70 | worker.removeAllListeners('error'); 71 | } 72 | 73 | worker.on('message', this.handleSuccess.bind(this, job)); 74 | worker.on('error', this.handleError.bind(this, job)); 75 | } 76 | 77 | handleCompletion(job) { 78 | this.currentJob = null; 79 | this.lastJob = job; 80 | 81 | this.emit('done', job); 82 | } 83 | 84 | handleSuccess(job, event) { 85 | this.currentJob.emit('done', event.data); 86 | this.threadPool.emit('done', event.data); 87 | this.handleCompletion(job); 88 | } 89 | 90 | handleError(job, errorEvent) { 91 | this.currentJob.emit('error', errorEvent); 92 | this.threadPool.emit('error', errorEvent); 93 | this.handleCompletion(job); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/ThreadPool/ThreadPool.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import EventEmitter from 'eventemitter3'; 4 | 5 | import Job from './Job'; 6 | import Thread from './Thread'; 7 | 8 | 9 | function runDeferred(callback) { 10 | setTimeout(callback, 0); 11 | } 12 | 13 | export default class ThreadPool extends EventEmitter { 14 | 15 | /** 16 | * @param {int} [size] Optional. Number of threads. Default is `ThreadPool.defaultSize`. 17 | * @param {String} [evalScriptUrl] Optional. URL to `evalWorker[.min].js` script (for IE compatibility). 18 | */ 19 | constructor(size, evalScriptUrl) { 20 | super(); 21 | 22 | size = size || ThreadPool.defaultSize; 23 | evalScriptUrl = evalScriptUrl || ''; 24 | 25 | this.size = size; 26 | this.evalWorkerUrl = evalScriptUrl; 27 | this.pendingJobs = []; 28 | this.idleThreads = []; 29 | this.activeThreads = []; 30 | 31 | for (var i = 0; i < size; i++) { 32 | let thread = new Thread(this); 33 | thread.on('done', this.handleThreadDone.bind(this, thread)); 34 | 35 | this.idleThreads.push(thread); 36 | } 37 | } 38 | 39 | terminateAll() { 40 | var allThreads = this.idleThreads.concat(this.activeThreads); 41 | 42 | allThreads.forEach(thread => { 43 | thread.terminate(); 44 | }); 45 | } 46 | 47 | /** 48 | * Usage: run ({String} WorkerScript [, {Object|scalar} Parameter[, {Object[]} BuffersToTransfer]] [, {Function} doneCallback(returnValue)]) 49 | * - or - 50 | * run ([{String[]} ImportScripts, ] {Function} WorkerFunction(param, doneCB) [, {Object|scalar} Parameter[, {Object[]} BuffersToTransfer]] [, {Function} DoneCallback(result)]) 51 | * 52 | * @return Job 53 | */ 54 | run(...args) { 55 | //////////////////// 56 | // Parse arguments: 57 | 58 | var workerScript, workerFunction, importScripts, parameter, transferBuffers, doneCb; 59 | var job; 60 | 61 | if (args.length < 1) { 62 | throw new Error('run(): Too few parameters.'); 63 | } 64 | 65 | if (typeof args[0] === 'string') { 66 | // 1st usage example (see doc above) 67 | workerScript = args.shift(); 68 | } else { 69 | // 2nd usage example (see doc above) 70 | if (typeof args[0] === 'object' && args[0] instanceof Array) { 71 | importScripts = args.shift(); 72 | } 73 | if (args.length > 0 && typeof args[0] === 'function') { 74 | workerFunction = args.shift(); 75 | } else { 76 | throw new Error('run(): Missing obligatory thread logic function.'); 77 | } 78 | } 79 | 80 | if (args.length > 0 && typeof args[0] !== 'function') { 81 | parameter = args.shift(); 82 | } 83 | if (args.length > 0 && typeof args[0] !== 'function') { 84 | transferBuffers = args.shift(); 85 | } 86 | if (args.length > 0 && typeof args[0] === 'function') { 87 | doneCb = args.shift(); 88 | } 89 | if (args.length > 0) { 90 | throw new Error('run(): Unrecognized parameters: ' + args); 91 | } 92 | 93 | /////////////// 94 | // Create job: 95 | 96 | if (workerScript) { 97 | job = new Job(workerScript, parameter, transferBuffers); 98 | } else { 99 | job = new Job(workerFunction, parameter, transferBuffers); 100 | if (importScripts && importScripts.length > 0) { 101 | job.setImportScripts(importScripts); 102 | } 103 | } 104 | 105 | if (doneCb) { 106 | job.on('done', doneCb); 107 | } 108 | 109 | //////////// 110 | // Run job: 111 | 112 | this.pendingJobs.push(job); 113 | runDeferred(this.runJobs.bind(this)); 114 | 115 | return job; 116 | } 117 | 118 | /** for internal use only */ 119 | runJobs() { 120 | if (this.idleThreads.length > 0 && this.pendingJobs.length > 0) { 121 | var thread = this.idleThreads.shift(); 122 | this.activeThreads.push(thread); 123 | 124 | var job = this.pendingJobs.shift(); 125 | thread.run(job); 126 | } 127 | } 128 | 129 | /** for internal use only */ 130 | handleThreadDone(thread) { 131 | this.idleThreads.unshift(thread); 132 | this.activeThreads.splice(this.activeThreads.indexOf(thread), 1); 133 | this.runJobs(); 134 | 135 | if (this.pendingJobs.length === 0 && this.activeThreads.length === 0) { 136 | this.emit('allDone'); 137 | } 138 | } 139 | 140 | /** @deprecated Use .removeAllListeners('done') instead */ 141 | clearDone() { 142 | this.removeAllListeners('done'); 143 | } 144 | 145 | /** Shortcut for .on('done', callback) */ 146 | done(callback) { 147 | return this.on('done', callback); 148 | } 149 | 150 | /** Shortcut for .on('error', callback) */ 151 | error(callback) { 152 | return this.on('error', callback); 153 | } 154 | 155 | /** Shortcut for .on('allDone', callback) */ 156 | allDone(callback) { 157 | return this.once('allDone', callback); 158 | } 159 | } 160 | 161 | 162 | ////////////////////// 163 | // Set default values: 164 | 165 | ThreadPool.defaultSize = 8; 166 | -------------------------------------------------------------------------------- /src/ThreadPool/WorkerFactory.js: -------------------------------------------------------------------------------- 1 | 2 | import EventEmitter from 'eventemitter3'; 3 | import Worker from './WorkerWrapper'; 4 | import genericWorker from './../genericWorker'; 5 | 6 | var genericWorkerDataUri = genericWorker.dataUri; 7 | var genericWorkerCode = genericWorker.genericWorkerCode; 8 | 9 | 10 | function runningInIE() { 11 | const olderIE = window.navigator.userAgent.indexOf('MSIE ') > -1; 12 | const newerIE = window.navigator.userAgent.indexOf('Trident/') > -1; 13 | 14 | return olderIE || newerIE; 15 | } 16 | 17 | 18 | export default class WorkerFactory extends EventEmitter { 19 | 20 | constructor(options) { 21 | super(); 22 | 23 | this.evalWorkerUrl = options.evalWorkerUrl; 24 | } 25 | 26 | runScriptFile(url, parameter, transferBuffers = []) { 27 | const worker = new Worker(url); 28 | this.emit('new', worker); 29 | 30 | this.passParamsToWorkerScript(worker, parameter, transferBuffers); 31 | 32 | return worker; 33 | } 34 | 35 | runCode(fn, parameter, importScripts = [], transferBuffers = []) { 36 | var worker; 37 | 38 | try { 39 | worker = new Worker(genericWorkerDataUri); 40 | } catch (error) { 41 | // Try to create the worker using evalworker.js if on IE 42 | if (runningInIE()) { 43 | if (!this.evalWorkerUrl) { 44 | throw new Error('No eval worker script set (required for IE compatibility).'); 45 | } 46 | 47 | worker = new Worker(this.evalWorkerUrl); 48 | 49 | // let the worker run the initialization code 50 | worker.postMessage(genericWorkerCode); 51 | } else { 52 | throw error; 53 | } 54 | } 55 | 56 | this.emit('new', worker); 57 | 58 | this.passParamsToGenericWorker(worker, fn, parameter, importScripts, transferBuffers); 59 | 60 | return worker; 61 | } 62 | 63 | passParamsToWorkerScript(worker, parameter, transferBuffers) { 64 | worker.postMessage(parameter, transferBuffers); 65 | } 66 | 67 | passParamsToGenericWorker(worker, fn, parameter, importScripts, transferBuffers) { 68 | worker.postMessage({ 69 | 'function' : fn, 70 | 'importScripts' : importScripts, 71 | 'parameter' : parameter 72 | }, transferBuffers); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/ThreadPool/WorkerWrapper.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'eventemitter3'; 2 | 3 | /** 4 | * Wrapping the WebWorker in an event emitter 5 | * (Because removeAllListeners() is quite a nice feature...) 6 | */ 7 | export default class WorkerWrapper extends EventEmitter { 8 | constructor(url) { 9 | super(); 10 | 11 | const worker = new Worker(url); 12 | this.worker = worker; 13 | 14 | worker.addEventListener('message', this.emit.bind(this, 'message')); 15 | worker.addEventListener('error', this.emit.bind(this, 'error')); 16 | } 17 | 18 | postMessage(...args) { 19 | this.worker.postMessage.apply(this.worker, args); 20 | } 21 | 22 | terminate() { 23 | return this.worker.terminate(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/evalworker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | self.onmessage = function(e) { 4 | eval(e.data); //eslint-disable-line no-eval 5 | }; 6 | -------------------------------------------------------------------------------- /src/genericWorker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*eslint-disable */ 4 | var genericWorkerCode = 5 | 'this.onmessage = function (event) {' + 6 | ' var fnData = event.data.function;' + 7 | ' var scripts = event.data.importScripts;'+ 8 | ' var fn = Function.apply(null, fnData.args.concat(fnData.body));' + 9 | ' if (importScripts && scripts.length > 0) {' + 10 | ' importScripts.apply(null, scripts);' + 11 | ' }' + 12 | ' fn(event.data.parameter, function(result) {' + 13 | ' postMessage(result);' + 14 | ' });' + 15 | '}'; 16 | /*eslint-enable */ 17 | 18 | var genericWorkerDataUri = 'data:text/javascript;charset=utf-8,' + encodeURI(genericWorkerCode); 19 | var createBlobURL = window.createBlobURL || window.createObjectURL; 20 | 21 | if (!createBlobURL) { 22 | var URL = window.URL || window.webkitURL; 23 | 24 | if (URL) { 25 | createBlobURL = URL.createObjectURL; 26 | } else { 27 | throw new Error('No Blob creation implementation found.'); 28 | } 29 | } 30 | 31 | if (typeof BlobBuilder === 'function' && typeof createBlobURL === 'function') { 32 | var blobBuilder = new BlobBuilder(); 33 | blobBuilder.append(genericWorkerCode); 34 | genericWorkerDataUri = createBlobURL(blobBuilder.getBlob()); 35 | } else if (typeof Blob === 'function' && typeof createBlobURL === 'function') { 36 | var blob = new Blob([ genericWorkerCode ], { type: 'text/javascript' }); 37 | genericWorkerDataUri = createBlobURL(blob); 38 | } 39 | 40 | export default { 41 | dataUri: genericWorkerDataUri, 42 | genericWorkerCode: genericWorkerCode 43 | }; 44 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 'use strict'; 3 | 4 | /** 5 | * Simple threadpool implementation based on web workers. 6 | * Loosely based on: http://www.smartjava.org/content/html5-easily-parallelize-jobs-using-web-workers-and-threadpool 7 | * 8 | * @author Andy Wermke 9 | * @see https://github.com/andywer/threadpool-js 10 | */ 11 | 12 | if ((typeof Worker === 'undefined' || Worker === null) && console) { 13 | console.log('Warning: Browser does not support web workers.'); 14 | } 15 | 16 | 17 | var ThreadPool = require('./ThreadPool/ThreadPool'); 18 | 19 | if (typeof define === 'function') { 20 | // require.js: 21 | define([], function () { return ThreadPool; }); 22 | } else if (typeof module === 'object') { 23 | module.exports = ThreadPool; 24 | } 25 | 26 | if (typeof window === 'object') { 27 | window.ThreadPool = ThreadPool; 28 | } 29 | --------------------------------------------------------------------------------