blob: f904c62e9510d5688f48f1d489a0f41774ba4b85 [file] [log] [blame]
/**
* worker must be started as a child process or a web worker.
* It listens for RPC messages from the parent process.
*/
// source of inspiration: https://github.com/sindresorhus/require-fool-webpack
var requireFoolWebpack = eval(
'typeof require !== \'undefined\'' +
' ? require' +
' : function (module) { throw new Error(\'Module " + module + " not found.\') }'
);
// create a worker API for sending and receiving messages which works both on
// node.js and in the browser
var worker = {};
if (typeof self !== 'undefined' && typeof postMessage === 'function' && typeof addEventListener === 'function') {
// worker in the browser
worker.on = function (event, callback) {
addEventListener(event, function (message) {
callback(message.data);
})
};
worker.send = function (message) {
postMessage(message);
};
}
else if (typeof process !== 'undefined') {
// node.js
var WorkerThreads;
try {
WorkerThreads = requireFoolWebpack('worker_threads');
} catch(error) {
if (typeof error === 'object' && error !== null && error.code === 'MODULE_NOT_FOUND') {
// no worker_threads, fallback to sub-process based workers
} else {
throw error;
}
}
if (WorkerThreads &&
/* if there is a parentPort, we are in a WorkerThread */
WorkerThreads.parentPort !== null) {
var parentPort = WorkerThreads.parentPort;
worker.send = parentPort.postMessage.bind(parentPort);
worker.on = parentPort.on.bind(parentPort);
} else {
worker.on = process.on.bind(process);
worker.send = process.send.bind(process);
// register disconnect handler only for subprocess worker to exit when parent is killed unexpectedly
worker.on('disconnect', function () {
process.exit(1);
});
}
}
else {
throw new Error('Script must be executed as a worker');
}
function convertError(error) {
return Object.getOwnPropertyNames(error).reduce(function(product, name) {
return Object.defineProperty(product, name, {
value: error[name],
enumerable: true
});
}, {});
}
/**
* Test whether a value is a Promise via duck typing.
* @param {*} value
* @returns {boolean} Returns true when given value is an object
* having functions `then` and `catch`.
*/
function isPromise(value) {
return value && (typeof value.then === 'function') && (typeof value.catch === 'function');
}
// functions available externally
worker.methods = {};
/**
* Execute a function with provided arguments
* @param {String} fn Stringified function
* @param {Array} [args] Function arguments
* @returns {*}
*/
worker.methods.run = function run(fn, args) {
var f = eval('(' + fn + ')');
return f.apply(f, args);
};
/**
* Get a list with methods available on this worker
* @return {String[]} methods
*/
worker.methods.methods = function methods() {
return Object.keys(worker.methods);
};
worker.on('message', function (request) {
try {
var method = worker.methods[request.method];
if (method) {
// execute the function
var result = method.apply(method, request.params);
if (isPromise(result)) {
// promise returned, resolve this and then return
result
.then(function (result) {
worker.send({
id: request.id,
result: result,
error: null
});
})
.catch(function (err) {
worker.send({
id: request.id,
result: null,
error: convertError(err)
});
});
}
else {
// immediate result
worker.send({
id: request.id,
result: result,
error: null
});
}
}
else {
throw new Error('Unknown method "' + request.method + '"');
}
}
catch (err) {
worker.send({
id: request.id,
result: null,
error: convertError(err)
});
}
});
/**
* Register methods to the worker
* @param {Object} methods
*/
worker.register = function (methods) {
if (methods) {
for (var name in methods) {
if (methods.hasOwnProperty(name)) {
worker.methods[name] = methods[name];
}
}
}
worker.send('ready');
};
if (typeof exports !== 'undefined') {
exports.add = worker.register;
}