別のJavascriptファイルがないWebワーカーですか?


291

私の知る限り、Webワーカーは別のJavaScriptファイルに記述し、次のように呼び出す必要があります。

new Worker('longrunning.js')

私はすべてのJavaScriptソースコードを組み合わせて縮小するためにクロージャーコンパイラーを使用しており、ワーカーを別のファイルに分散して配布する必要はありません。これを行う方法はありますか?

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

ファーストクラスの関数がJavaScriptにとって非常に重要であることを考えると、バックグラウンド作業を行う標準的な方法では、なぜWebサーバーから他のJavaScriptファイル全体をロードする必要があるのでしょうか。


7
これは、実行コンテキストを純粋にスレッドセーフに保つことが、ファーストクラスの関数よりもさらに重要であるためです。-)
Pointy

1
私はそれに取り組んでいます(または問題を最小限に抑えるために):DynWorker。あなたは行うことができますvar worker = new DynWorker(); worker.inject("foo", function(){...});...
フェリックスSaparelli


1
OPは、「JavaScriptソースファイルではなく関数を受け入れるようにワーカーに教える」という質問を削除しました。答えはここに
Rob W

これをはるかに簡単にするためにtask.jsを開発しました。ほとんどの場合、小さなロックタスクのオフロードのみを試みます。
Chad Scira 2016

回答:


225

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

オンザフライでワーカースクリプトを作成する場合、または個別のワーカーファイルを作成せずに自己完結型のページを作成する場合はどうでしょうか。Blob()を使用すると、ワーカーコードへのURLハンドルを文字列として作成することにより、メインロジックと同じHTMLファイルにワーカーを「インライン化」できます。


BLOBインラインワーカーの完全な例:

<!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>


Google Chromeのみのソリューション、Firefox 10でサポートされるようです。他のブラウザーについてはわかりません
4esn0k

2
BlobBuiler は非推奨になりました。代わりにBlobを使用してください。現在、最新のFirefox / WebKit / OperaおよびIE10でサポートされています。古いブラウザーの互換性表を参照してください。
フェリックスSaparelli

3
BlobコンストラクターはIE10でサポートされている可能性がありますが、それを介してJavaScriptをWebワーカーに渡すことはできません(IE11でも可能です):connect.microsoft.com/IE/feedback/details/801810/…
jayarjo 2014

1
@albanx-どのようなテストですか?すでにオンラインで数十億のデモページがあり、スレッド化が何年もの間ブラウザをハングアップさせないことを示しています。
vsync 2014年

2
@albanx-少なくともどの難解なブラウザを使用していて、どのハングが使用されているかを伝えたいですか?このデモはあなたのためにハングアップしますか?ie.microsoft.com/testdrive/Graphics/WorkerFountains/...
VSYNC

162

WebワーカーコードをHTMLに埋め込むhtml5rocksソリューションは、かなり恐ろしいものです。
また、エスケープされたJavaScript-as-a-stringのblobは、ワークフローを複雑にするため、特に良くありません(Closureコンパイラーは文字列を操作できません)。

個人的にはtoStringメソッドが本当に好きですが、@ dan-man正規表現です!

私の好ましいアプローチ:

// 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 );

サポートは、次の3つのテーブルの共通部分です。

ただし、これはSharedWorkerでは機能しません。オプションの 'name'パラメーターが一致しても、URLは完全に一致する必要があるためです。SharedWorkerの場合、別個のJavaScriptファイルが必要です。


2015年の更新-ServiceWorkerの特異点が到着しました

この問題を解決するさらに強力な方法があります。ここでも、ワーカーコードを(静的文字列ではなく)関数として保存し、.toString()を使用して変換してから、選択した静的URLの下のCacheStorageにコードを挿入します。

// 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'}})
 );
});

2つのフォールバックが考えられます。上記のObjectURL、またはよりシームレスに、実際の / my_workers / worker1.jsに JavaScriptファイルを配置します

このアプローチの利点は次のとおりです。

  1. SharedWorkersもサポートできます。
  2. タブは、固定アドレスで単一のキャッシュされたコピーを共有できます。blobアプローチは、すべてのタブに対してランダムなオブジェクトURLを増殖させます。

4
このソリューションでは、ブラウザの互換性はどのようになりますか?
Ben Dilts 2013年

このソリューションについて詳しく説明していただけますか、それはどのように機能しますか?worker1.jsとは何ですか?それは別のjsファイルですか?これを使用しようとしていますが、機能させることができません。具体的には、SharedWorkerで機能するようにしようとしています
Yehuda

あなたがそれを便利な機能にまとめることができれば!
mmm

@ Ben Dilts:ブラウザの互換性は、babeljs.io
Jack Giffin

標準では、Function.prototype.toString()が関数本体を文字列として返すことは保証されていません。おそらく回答に警告を追加する必要があります。
RD

37

実行コンテキストを認識し、親スクリプトとワーカーの両方として機能する単一の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はスクリプトではなく親ページへの相対パスであるため、やや扱いにくい計算を使用して、親ページに対するスクリプトのパスを正確に計算します。


4
あなたのサイトは消えたようです。新しいURLはありますか?
BrianFreud 2012

1
これは興味深いアプローチです。FWIW、私は「ウィンドウ」に対して「自己」(Webワーカーグローバルオブジェクト)の存在をチェックすることにより、Webワーカーを機能検出します。
pwnall 2013年

PapaParseがWebワーカーを処理する方法を調査してきましたが、彼らはこのアプローチを採用し
JP DeVries

'typeof importScripts!== null'を使用してテストすると、スクリプトがワーカースコープで実行されているかどうかがわかります。
MeTTeO

1
以前のSiblingがスクリプト要素から何であるかわかりません。誰かが私を説明できますか?
Teemoh 2017年

28

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:

上記のリンクは私が作成した要点です。他の誰かが後でそれを実際のリポジトリに変えました。


11

Webワーカーは、個別のプログラムとして完全に別のコンテキストで動作します。

つまり、他のコンテキストに属するクロージャーを介してオブジェクトを参照できるため、コードをオブジェクト形式であるコンテキストから別のコンテキストに移動することはできません。
ECMAScriptはシングルスレッド言語として設計されているため、これは特に重要です。Webワーカーは別々のスレッドで動作するため、スレッドセーフでない操作が実行されるリスクがあります。

これも、Webワーカーをソース形式のコードで初期化する必要があることを意味します。

WHATWGの仕様によると

結果の絶対URLの起点が入力スクリプトの起点と同じでない場合は、SECURITY_ERR例外をスローします。

したがって、スクリプトは元のページと同じスキームの外部ファイルである必要があります。data:URLまたはjavascript:URLからスクリプトをロードすることはできず、https:ページはhttp:URLのスクリプトを使用してワーカーを開始できませんでした。

残念ながら、ソースコード付きの文字列をコンストラクタに渡すことができなかった理由は、実際には説明されていません。


6

インラインワーカー向けの読み方

    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"); 

私がしたことは、すべてのワーカーコードを使用して関数を作成し、その関数toString()を渡し、本体を抽出して、それをBlobに配置することでした。最後の答えを確認してください、私には例があります
フェルナンドカルバハル2018年

5

Adriaの応答を取得して、現在のChromeおよびFFで機能するがIE10では機能しないコピー/貼り付け可能な関数に配置します(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/


5

最近の回答(2018)

Greenletを使用できます。

非同期関数を独自のスレッドに移動します。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'))

3

ユースケースに応じて、次のようなものを使用できます

task.jsすべてのコア(node.js、およびWeb)で実行されるCPU集中型コードを取得するための簡略化されたインターフェース

例は

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
});

2

vkThreadプラグインを見てください。htisプラグインを使用すると、メインコードで任意の関数を取得し、スレッド(ウェブワーカー)で実行できます。したがって、特別な「Webワーカーファイル」を作成する必要はありません。

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

-ヴァディム



1

これを行うためのより良い方法は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"); 


1

ここでコンソール:

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);
});

1

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
    });
  };

})();


1

ES6のテンプレートリテラルのおかげで、これにはもう1つの素晴らしいオプションがあると思います。これにより、余分なワーカー関数(およびその奇妙なスコープ)を省き、ワーカーを対象とするコードを、テキストを格納するために使用していた場合と同じように、実際にはドキュメントや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);
`;

これがそのアプローチの残りの要点です

それらを配列に収集し、それぞれに対して.toStringを実行してそれらを文字列に減らすことで、必要な追加の関数依存関係をワーカーに取り込むことができます(それらが関数宣言である限り機能するはずです)。次に、スクリプト文字列の前に追加します。こうすることで、作成しているコードのスコープに既にバンドルされている可能性のあるスクリプトをインポートする必要がなくなります。

この特定のバージョンの唯一の欠点は、リンターがサービスワーカーコードをリントできないことです(これは単なる文字列であるため)。これは、「個別のワーカー関数アプローチ」の利点です。


1

これは上記の単なる追加です-jsFiddleでWebワーカーをテストするための素晴らしいテンプレートがあります。Blobではなく、jsFiddles ?jsapiを使用します。

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);
  }
})

通常のウェブワーカーテンプレートと共有ワーカーテンプレートが利用可能です。


1

私はCodePenが現在構文<script>ではないインラインタグを強調表示していないことを発見しましたtype="text/javascript"(またはtype属性を持たない)。

そこで、ラッパー関数を作成せずにタグから救済できる唯一の方法である、ラベル付きブロックを使用して、似ているが少し異なる解決策を考案しました(これは不要です)。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>


1

Function#callAsWorkerthisArgと引数(のようにcall)を取り、promise を返す単純な約束されたバージョン:

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);
});


close()メソッドを追加して、Webワーカーのライフフックを閉じる必要があります。developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/...
シャハルドーンレヴィ

@ShaharドーンLevi、このclose関数は非推奨です。ただし、労働者を解雇することができます。今追加しました。
トリンコット

0

私はこのようなコードを使用しています。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;
}


私の答えを見てください、私はそれをやっただけですが、コールバックを渡す方法を抽象化するためにクラス全体を書きました
Fernando Carvajal

0

はい、可能です。Blobファイルを使用してコールバックを渡しました。

私が作成したクラスの機能と、それがバックグラウンドでのコールバックの実行を管理する方法を紹介します。

まず、GenericWebWorkerで実行するコールバックに渡したいデータを使用してインスタンス化します。Web Workerこれには、使用する関数、この場合は数値、日付、呼び出される関数が含まれますblocker

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

このブロッカー関数は、nミリ秒の間無限に実行されます

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


0

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" })));

これは、何らかの理由でワーカーに個別のスクリプトタグを使用したくない場合に便利です。


0

別の解決策は、ワーカーを関数でラップし、次のように関数を呼び出す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.

-1

ワーカーで関数を実行するためのワンライナー:

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

使用例:

let fn = FunctionalWorker(() => {
    self.postMessage("hi");
});
fn.onmessage = msg => {
    console.log(msg);
};
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.