Web工作者没有一个单独的Javascript文件?

据我所知,web worker需要写在一个单独的JavaScript文件中,并像这样调用:

new Worker('longrunning.js')

我正在使用闭包编译器来合并和缩小我所有的JavaScript源代码,我不希望将我的worker放在单独的文件中进行分发。有什么办法可以做到吗?

new Worker(function() {
//Long-running work here
});

既然一级函数对JavaScript如此重要,为什么标准的后台工作方式必须从web服务器加载整个其他JavaScript文件呢?

110500 次浏览

网络工作者在完全独立的上下文中操作,就像单个程序一样。

这意味着代码不能以对象的形式从一个上下文中移动到另一个上下文中,因为它们可以通过属于另一个上下文中的闭包引用对象 这一点尤其重要,因为ECMAScript被设计成一种单线程语言,并且由于web worker在单独的线程中操作,那么您将有执行非线程安全操作的风险。< / p >

这再次意味着web工作者需要用源代码形式的代码进行初始化。

来自WHATWG的规范说

如果原点的结果 绝对URL是不一样的 输入脚本的起源,然后抛出

. a SECURITY_ERR异常 因此,脚本必须是外部文件 和原来一样的方案 页面:您无法从 data: URL或javascript: URL,和一个 页面无法启动工作 使用脚本http: url .

但不幸的是,它并没有真正解释为什么不允许将带有源代码的字符串传递给构造函数。

http://www.html5rocks.com/en/tutorials/workers/basics/#toc-inlineworkers

如果您希望动态地创建工作人员脚本,或者创建一个自包含的页面,而不必创建单独的工作人员文件,该怎么办?使用Blob(),您可以“inline”;通过将工作代码的URL句柄创建为字符串,将工作代码放在与主逻辑相同的HTML文件中


BLOB inline worker的完整示例:

<!DOCTYPE html>
<script id="worker1" type="javascript/worker">
// This script won't be parsed by JS engines because its type is javascript/worker.
self.onmessage = function(e) {
self.postMessage('msg from worker');
};
// Rest of your worker code goes here.
</script>
<script>
var blob = new Blob([
document.querySelector('#worker1').textContent
], { type: "text/javascript" })


// Note: window.webkitURL.createObjectURL() in Chrome 10+.
var worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(e) {
console.log("Received: " + e.data);
}
worker.postMessage("hello"); // Start the worker.
</script>

您可以创建一个JavaScript文件,该文件可以感知其执行上下文,并且可以充当父脚本和工作脚本。让我们从这样一个文件的基本结构开始:

(function(global) {
var is_worker = !this.document;
var script_path = is_worker ? null : (function() {
// append random number and time to ID
var id = (Math.random()+''+(+new Date)).substring(2);
document.write('<script id="wts' + id + '"></script>');
return document.getElementById('wts' + id).
previousSibling.src;
})();
function msg_parent(e) {
// event handler for parent -> worker messages
}
function msg_worker(e) {
// event handler for worker -> parent messages
}
function new_worker() {
var w = new Worker(script_path);
w.addEventListener('message', msg_worker, false);
return w;
}
if (is_worker)
global.addEventListener('message', msg_parent, false);


// put the rest of your library here
// to spawn a worker, use new_worker()
})(this);

正如你所看到的,这个脚本包含了父对象和工作对象的所有代码,检查它自己的实例是否是带有!document的工作对象。有点笨拙的script_path计算用于精确计算脚本相对于父页的路径,因为提供给new Worker的路径相对于父页,而不是脚本。

你可以在同一个javascript文件中使用内联的webworker。

下面的文章将帮助您轻松理解webworker及其局限性和调试webworker。

掌握网络工作者

使用Blob方法,对于工人工厂来说是这样的:

var BuildWorker = function(foo){
var str = foo.toString()
.match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1];
return  new Worker(window.URL.createObjectURL(
new Blob([str],{type:'text/javascript'})));
}

所以你可以这样使用它…

var myWorker = BuildWorker(function(){
//first line of worker
self.onmessage(){....};
//last line of worker
});

编辑:

我只是进一步扩展了这个想法,使它更容易进行跨线程通信:bridged-worker.js

编辑2:

上面的链接是我创建的一个要点。后来又有人把它变成了实际回购

我认为更好的方法是使用Blob对象,下面你可以看到一个简单的例子。

// create a Blob object with a worker code
var blob = new Blob(["onmessage = function(e) { postMessage('msg from worker'); }"]);


// Obtain a blob URL reference to our worker 'file'.
var blobURL = window.URL.createObjectURL(blob);


// create a Worker
var worker = new Worker(blobURL);
worker.onmessage = function(e) {
console.log(e.data);
};
worker.postMessage("Send some Data");

一个更好的阅读方式为内联工人..

    var worker_fn = function(e)
{
self.postMessage('msg from worker');
};


var blob = new Blob(["onmessage ="+worker_fn.toString()], { type: "text/javascript" });


var worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(e)
{
alert(e.data);
};
worker.postMessage("start");

看一下vkThread插件。使用htis插件,你可以在主代码中获取任何函数,并在线程(web worker)中执行它。所以,你不需要创建一个特殊的“web-worker文件”。

http://www.eslinstructor.net/vkthread/

——瓦迪姆

html5rocks在HTML中嵌入web worker代码的解决方案相当可怕。
而一团转义的JavaScript-as-a-string也没有更好,尤其是因为它使工作流程复杂化(闭包编译器不能操作字符串)

我个人非常喜欢toString方法,但是@dan-man THAT正则表达式!

我喜欢的方法是:

// Build a worker from an anonymous function body
var blobURL = URL.createObjectURL( new Blob([ '(',


function(){
//Long-running work here
}.toString(),


')()' ], { type: 'application/javascript' } ) ),


worker = new Worker( blobURL );


// Won't be needing this anymore
URL.revokeObjectURL( blobURL );

支持是这三个表的交集:

然而,这对SharedWorker无效,因为URL必须是精确匹配的,即使可选的'name'参数匹配。对于SharedWorker,您需要一个单独的JavaScript文件。


2015年更新——ServiceWorker奇点到来

现在有一个更有效的方法来解决这个问题。 同样,将工作代码存储为函数(而不是静态字符串),并使用. tostring()进行转换,然后将代码插入CacheStorage中您选择的静态URL下
// Post code from window to ServiceWorker...
navigator.serviceWorker.controller.postMessage(
[ '/my_workers/worker1.js', '(' + workerFunction1.toString() + ')()' ]
);


// Insert via ServiceWorker.onmessage. Or directly once window.caches is exposed
caches.open( 'myCache' ).then( function( cache )
{
cache.put( '/my_workers/worker1.js',
new Response( workerScript, { headers: {'content-type':'application/javascript'}})
);
});

有两种可能的退路。ObjectURL和上面一样,或者更无缝地将真正的 JavaScript文件放在/my_workers/worker1.js

这种方法的优点是:

  1. 还可以支持SharedWorkers。
  2. 选项卡可以在固定地址共享单个缓存副本。blob方法为每个选项卡增加随机objecturl。

尝试使用jThread。https://github.com/cheprasov/jThread

// You can use simple calling like this
jThread(
function(arr){
//... some code for Worker
return arr;
}
,function(arr){
//... done code
}
)( [1,2,3,4,5,6,7] ); // some params

控制台:

var worker=new Worker(window.URL.createObjectURL(new Blob([function(){
//Long-running work here
postMessage('done');
}.toString().split('\n').slice(1,-1).join('\n')],{type:'text/javascript'})));


worker.addEventListener('message',function(event){
console.log(event.data);
});

将Adria的响应放在一个可复制粘贴的函数中,该函数适用于当前的Chrome和FF,但不适用于IE10 (worker from blob导致安全错误)。

var newWorker = function (funcObj) {
// Build a worker from an anonymous function body
var blobURL = URL.createObjectURL(new Blob(
['(', funcObj.toString(), ')()'],
{type: 'application/javascript'}
));


var worker = new Worker(blobURL);


// Won't be needing this anymore
URL.revokeObjectURL(blobURL);


return worker;
}

下面是一个工作示例http://jsfiddle.net/ubershmekel/YYzvr/

https://developer.mozilla.org/es/docs/Web/Guide/Performance/Using_web_workers

    // Syntax: asyncEval(code[, listener])


var asyncEval = (function () {


var aListeners = [], oParser = new Worker("data:text/javascript;charset=US-ASCII,onmessage%20%3D%20function%20%28oEvent%29%20%7B%0A%09postMessage%28%7B%0A%09%09%22id%22%3A%20oEvent.data.id%2C%0A%09%09%22evaluated%22%3A%20eval%28oEvent.data.code%29%0A%09%7D%29%3B%0A%7D");


oParser.onmessage = function (oEvent) {
if (aListeners[oEvent.data.id]) { aListeners[oEvent.data.id](oEvent.data.evaluated); }
delete aListeners[oEvent.data.id];
};




return function (sCode, fListener) {
aListeners.push(fListener || null);
oParser.postMessage({
"id": aListeners.length - 1,
"code": sCode
});
};


})();

使用我的小插件https://github.com/zevero/worker-create

var worker_url = Worker.createURL(function(e){
self.postMessage('Example post from Worker'); //your code here
});
var worker = new Worker(worker_url);

所以我认为我们现在有了另一个很酷的选择,这要感谢ES6中的模板文字。这允许我们省去额外的worker函数(及其奇怪的作用域),只需将用于worker的代码编写为多行文本,就像我们用来存储文本的情况一样,但实际上不需要使用文档或DOM来实现。例子:

const workerScript = `
self.addEventListener('message', function(e) {
var data = e.data;
console.log('worker recieved: ',data);
self.postMessage('worker added! :'+ addOne(data.value));
self.close();//kills the worker
}, false);
`;

这是一个这是其他方法的要点

请注意,我们可以将任何我们想要的额外函数依赖项拉入worker,只需将它们收集到一个数组中,并对每个函数运行. tostring,将它们简化为字符串(只要它们是函数声明就可以工作),然后将其添加到脚本字符串中。这样,我们就不必导入我们可能已经捆绑到正在编写的代码范围中的脚本。

这个特定版本唯一的缺点是lints无法检测service worker代码(因为它只是一个字符串),这对于“单独的worker函数方法”来说是一个优势。

这只是上面的一个补充-我在jsFiddle中有一个很好的模板用于测试web worker。而不是Blob,它使用jsFiddles ?js api:

function workerFN() {
self.onmessage = function(e) {
switch(e.data.name) {
case "" :
break;
default:
console.error("Unknown message:", e.data.name);
}
}
}
// This is a trick to generate real worker script that is loaded from server
var url = "/echo/js/?js="+encodeURIComponent("("+workerFN.toString()+")()");
var worker = new Worker(url);
worker.addEventListener("message", function(e) {
switch(e.data.name) {
case "" :
break;
default:
console.error("Unknown message:", e.data.name);
}
})

普通的网络工作者共享的工人模板可用。

根据您的用例,您可以使用类似

task.js .js简化的界面,让CPU密集型代码运行在所有核(node.js和web)上

一个例子是

function blocking (exampleArgument) {
// block thread
}


// turn blocking pure function into a worker task
const blockingAsync = task.wrap(blocking);


// run task on a autoscaling worker pool
blockingAsync('exampleArgumentValue').then(result => {
// do something with result
});

我发现CodePen目前没有语法高亮显示非type="text/javascript"(或没有类型属性)的内联<script>标记。

所以我设计了一个类似但略有不同的解决方案,使用标记块break,这是你可以摆脱<script>标记而不创建包装器函数的唯一方法(这是不必要的)。

<!DOCTYPE html>
<script id="worker1">
worker: { // Labeled block wrapper


if (typeof window === 'object') break worker; // Bail if we're not a Worker


self.onmessage = function(e) {
self.postMessage('msg from worker');
};
// Rest of your worker code goes here.
}
</script>
<script>
var blob = new Blob([
document.querySelector('#worker1').textContent
], { type: "text/javascript" })


// Note: window.webkitURL.createObjectURL() in Chrome 10+.
var worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(e) {
console.log("Received: " + e.data);
}
worker.postMessage("hello"); // Start the worker.
</script>

我使用这样的代码,你可以将onmessage定义为一个函数而不是纯文本,这样编辑器就可以突出显示你的代码和jshint工作。

const worker = createWorker();


createWorker() {
const scriptContent = getWorkerScript();
const blob = new Blob([
scriptContent,
], {
type: "text/javascipt"
});
const worker = new Worker(window.URL.createObjectURL(blob));
return worker;
}


getWorkerScript() {
const script = {
onmessage: function (e) {
console.log(e);
let result = "Hello " + e.data
postMessage(result);
}
};
let content = "";
for (let prop in script){
content += `${prop}=${script[prop].toString()}`;
}
return content;
}

是的,这是可能的,我做到了使用Blob文件和传递回调

我将向您展示我编写的一个类是做什么的,以及它如何在后台管理回调的执行。

首先,你用你想传递给回调的任何数据实例化GenericWebWorker,这些数据将在Web Worker中执行,其中包括你想使用的函数,在本例中是一个数字、一个日期和一个名为blocker的函数

var worker = new GenericWebWorker(100, new Date(), blocker)

这个阻塞器函数将在n毫秒内执行一个无限while

function blocker (ms) {
var now = new Date().getTime();
while(true) {
if (new Date().getTime() > now +ms)
return;
}
}

然后像这样用

worker.exec((num, date, fnBlocker) => {
/*Everithing here does not block the main thread
and this callback has access to the number, date and the blocker */
fnBlocker(10000) //All of this run in backgrownd
return num*10


}).then(d => console.log(d)) //Print 1000

现在,是时候看看下面例子中的神奇之处了

/*https://github.com/fercarvo/GenericWebWorker*/
class GenericWebWorker {
constructor(...ags) {
this.args = ags.map(a => (typeof a == 'function') ? {type:'fn', fn:a.toString()} : a)
}


async exec(cb) {
var wk_string = this.worker.toString();
wk_string = wk_string.substring(wk_string.indexOf('{') + 1, wk_string.lastIndexOf('}'));
var wk_link = window.URL.createObjectURL( new Blob([ wk_string ]) );
var wk = new Worker(wk_link);


wk.postMessage({ callback: cb.toString(), args: this.args });
 

var resultado = await new Promise((next, error) => {
wk.onmessage = e => (e.data && e.data.error) ? error(e.data.error) : next(e.data);
wk.onerror = e => error(e.message);
})


wk.terminate(); window.URL.revokeObjectURL(wk_link);
return resultado
}


async parallel(arr, cb) {
var res = [...arr].map(it => new GenericWebWorker(it, ...this.args).exec(cb))
var all = await Promise.all(res)
return all
}


worker() {
onmessage = async function (e) {
try {
var cb = new Function(`return ${e.data.callback}`)();
var args = e.data.args.map(p => (p.type == 'fn') ? new Function(`return ${p.fn}`)() : p);


try {
var result = await cb.apply(this, args); //If it is a promise or async function
return postMessage(result)


} catch (e) { throw new Error(`CallbackError: ${e}`) }
} catch (e) { postMessage({error: e.message}) }
}
}
}




function blocker (ms) {
var now = new Date().getTime();
while(true) {
if (new Date().getTime() > now +ms)
return;
}
}


setInterval(()=> console.log("Not blocked " + Math.random()), 1000)


console.log("\n\nstarting blocking code in Worker\n\n")


var worker = new GenericWebWorker(100, new Date(), blocker)


worker.exec((num, date, fnBlocker) => {
fnBlocker(7000) //All of this run in backgrownd
return num*10
})
.then(d => console.log(`\n\nEnd of blocking code: result ${d}\n\n`)) //Print 1000

一个简单的承诺版本,Function#callAsWorker,它接受一个thisArg和参数(就像call一样),并返回一个承诺:

Function.prototype.callAsWorker = function (...args) {
return new Promise( (resolve, reject) => {
const code = `self.onmessage = e => self.postMessage((${this.toString()}).call(...e.data));`,
blob = new Blob([code], { type: "text/javascript" }),
worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = e => (resolve(e.data), worker.terminate());
worker.onerror = e => (reject(e.message), worker.terminate());
worker.postMessage(args);
});
}


// Demo
function add(...nums) {
return nums.reduce( (a,b) => a+b );
}
// Let the worker execute the above function, with the specified arguments
add.callAsWorker(null, 1, 2, 3).then(function (result) {
console.log('result: ', result);
});

近期答案(2018)

你可以使用一种绿色小鸟:

移动一个异步函数到它自己的线程中。Workerize的简化单函数版本。

例子:

import greenlet from 'greenlet'


const getName = greenlet(async username => {
const url = `https://api.github.com/users/${username}`
const res = await fetch(url)
const profile = await res.json()
return profile.name
})


console.log(await getName('developit'))

你可以把worker.js文件的内容放在反勾号(允许多行字符串常量)中,然后像这样创建一个blob:

var workerScript = `
self.onmessage = function(e) {
self.postMessage('message from worker');
};
// rest of worker code goes here
`;


var worker =
new Worker(createObjectURL(new Blob([workerScript], { type: "text/javascript" })));

如果出于某种原因,您不想为worker使用单独的脚本标记,那么这是非常方便的。

另一个解决方案是将Worker包装在一个函数中,然后创建一个调用函数的blob,如下所示:

     function workerCode() {
self.onmessage = function (e) {
console.log("Got message from parent", e.data);
};
setTimeout(() => {
self.postMessage("Message From Worker");
}, 2000);
}


let blob = new Blob([
"(" + workerCode.toString() + ")()"
], {type: "text/javascript"});


// Note: window.webkitURL.createObjectURL() in Chrome 10+.
let worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function (e) {
console.log("Received: " + e.data);
};
worker.postMessage("hello"); // Start the worker.

在worker中运行函数的一行代码:

const FunctionalWorker = fn => new Worker(window.URL.createObjectURL(new Blob(["(" + fn.toString() + ")()"], {type: "text/javascript"})));

使用示例:

let fn = FunctionalWorker(() => {
self.postMessage("hi");
});
fn.onmessage = msg => {
console.log(msg);
};

这是一个有点离题的答案,但是……您可能不需要使用网络工作者来处理浏览器上的长时间工作。

让我们假设你想要运行几次繁重的计算(就像你对数组做的那样):

const heavyFct = () => {let i = 0; while(i<1e8) {++i}}


for (let i = 0; i < 100; ++i) {
heavyFct();
}

这将冻结您的浏览器。

为了避免这种情况,我们可以这样依赖setTimeout:

const desync = (i = 0) => {
if (i >= 100) {return}
heavyFct();
setTimeout(() => desync(i + 1), 0);
}
desync();

现在,您可以在不冻结计算机的情况下运行繁重的计算

有一些答案,但这里是另一个内联版本。

注:“self"参数纯粹是为了检测目的而修饰的,实际的worker代码在第一个大括号之后开始,self是正常的

inlineWorker (
"hello world",// initial message to send to worker
function(self){
// inline worker code.
self.onmessage = function (e) {
  

self.postMessage("thinking...");
for (var i=0;i<100000000;i++) {
var r = Math.random();
}
self.postMessage(e.data.toUpperCase());
}


},function(e){
// optional message handler
document.getElementById("log").innerHTML= "from worker:"+e.data;
});






function inlineWorker (msg,fn,onMsg) {
var
w=window,
U=!!w.webkitURL?w.webkitURL:w.URL,
src=fn.toString(),
s=src.indexOf('{'),
e=src.lastIndexOf('}'),
worker = new Worker(U.createObjectURL(
new Blob([ src.substring(s+1,e-1) ], { type: "text/javascript" })
));
if (typeof onMsg==="function") {
worker.addEventListener("message",onMsg);
}
if (msg) {
worker.postMessage(msg);
}
return worker;
}
<div id="log"></div>

我的看法是:

function BuildWorker(fn){
var str = fn.toString().match(/^[^{]+{([\s\S]+)}\s*$/m)[1];
return  new Worker(window.URL.createObjectURL(
new Blob([str],{type:'text/javascript'})));
}


function createAsyncWorker(fn){
    

// asyncworker=createAsyncWorker(function(){
//     importScripts('my_otherscript.js');
//     self.onmessage = function([arg1,arg2]) {
//         self.postMessage('msg from worker');
//     };
// })
// await asyncworker.postMessage('arg1','value')
// await asyncworker.postMessage('arg1','value')
// asyncworker.worker.terminate()
    

var worker = BuildWorker(fn);


function postMessage(...message){
let external={}, promise= new Promise((resolve,reject)=>{external.resolve=resolve;external.reject=reject;})
worker.onmessage = function(message){ external.resolve(message.data)};
worker.postMessage(message); // Start the worker.
return promise;
}


return {worker,postMessage};
}

使用的例子:

autoarima = createAsyncWorker(function(){
importScripts("https://127.0.0.1:11000/arima.js")
    

self.onmessage=(message)=>{
let [action,arg1,arg2]=message.data
if(action=='load')
{
ARIMAPromise.then(ARIMA1 => {
ARIMA=ARIMA1
autoarima = new ARIMA({ auto: true });
//   const ts = Array(10).fill(0).map((_, i) => i + Math.random() / 5)
//   const arima = new ARIMA({ p: 2, d: 1, q: 2, P: 0, D: 0, Q: 0, S: 0, verbose: false }).train(ts)
//   const [pred, errors] = arima.predict(10)
postMessage('ok')
});
}
if(action=='fit')
{
autoarima.fit(arg1)
postMessage('ok')
}
if(action=='predict')
{
postMessage(autoarima.predict(arg1,arg2))
}
};
})
autoarima.terminate=function(){  this.worker.terminate(); }
autoarima.load=async function(...args){return await this.postMessage('load',...args)}
autoarima.fit=async function(...args){return await this.postMessage('fit',...args)}
autoarima.predict=async function(...args){return await this.postMessage('predict',...args)}


await autoarima.load()
await autoarima.fit(b_values)
await autoarima.predict(1)

我喜欢ifbamoq给出的答案,但由于堆栈溢出的积分政策,我无法评论。因此,我将给出一个示例,展示一些正在进行的密集工作——以及它如何不锁定主线程。

如果你像我一样双击html文件,把它们当成小程序,就不会遇到空原点的CORS问题。: -)

<!DOCTYPE html>
<html>
<head>
<title>Worker example: One-core computation</title>
</head>
<body>
<p>The highest prime number discovered so far is: <div id="result"></div></p>
</body>


<script>
// let worker = new Worker('WebWorker.js');  // lets skip this to avoid null origin issues


let WorkerFn = (event) =>
{
let isPrime = false;


for (let n = 2; n <= 1_000_000; n++)
{
isPrime = true;


for(let i = 2; i <= Math.sqrt(n); i++)
if (n % i == 0)
isPrime = false;   // If you can get thru all this shit and survive, ur prime!


if (isPrime)
postMessage(n);
}
}


let worker = new Worker(window.URL.createObjectURL(new Blob(["(" + WorkerFn.toString() + ")()"], {type: "text/javascript"})));


worker.onmessage = (event) =>
{
result.innerHTML = event.data;
}


</script>
</html>

对于Node.js实现,可以使用以下对Trincot的回答的改编。再次注意,Function.prototype.callAsWorker()接受thisArg和参数,就像Function.prototype.call()一样,并返回一个承诺。

const { Worker } = require ( 'worker_threads' );


Function.prototype.callAsWorker = function ( ...args ) {
return new Promise( ( resolve, reject ) => {


const code = `
const { parentPort, workerData } = require ( 'worker_threads' );
parentPort.postMessage( ( ${this.toString()} ).call( ...workerData ) )
`;
const worker = new Worker( code, { eval: true, workerData: args } );
            

worker.on('message', ( msg ) => { resolve( msg ), worker.terminate() } );
worker.on('error', ( err ) => { reject( err ), worker.terminate() } );
worker.on('exit', ( code ) => {
if ( code !== 0 ) {
reject( new Error( `Worker stopped with exit code ${code}.` ) );
}
});


});
}


// Demo
function add( ...nums ) {
return nums.reduce( ( a, b ) => a + b );
}


// Let the worker execute the above function, with the specified arguments
let result = await add.callAsWorker( null, 1, 2, 3 );
console.log( 'result: ', result );

@Trincot's似乎是目前为止最好的。然而,也许我们可以进一步发展它。所以我的想法是,

  1. 我们不修改Function.prototype
  2. 为线程操作获取函数的承诺/线程化版本。
  3. 如果需要,请确保仍然可以同步调用该函数。

因此,我们用spawn方法定义了一个Threadable类。一旦我们将函数设为该类的成员,那么它就是可线程的:)

class Threadable extends Function {
  

constructor(f){
super("...as",`return ${f.toString()}.apply(this,as)`);
}
  

spawn(...as){
var code = `self.onmessage = m => self.postMessage((${this.toString()}).apply(self,m.data));`,
blob = new Blob([code], {type: "text/javascript"}),
wrkr = new Worker(window.URL.createObjectURL(blob));
return new Promise( (v,x) => ( wrkr.onmessage = m => (v(m.data), wrkr.terminate())
, wrkr.onerror   = e => (x(e.message), wrkr.terminate())
, wrkr.postMessage(as)
)
);
}
}


function add(...nums) {
return nums.reduce((a,b) => a+b);
}


var addT = new Threadable(add);


addT.spawn(1,2,3,4)
.then(m => console.log(`Promisified thread returned ${m}`));


console.log(`Synchronous invocation of addT returned ${addT(1,2,3,4)}`);