17

多くの異なるタブに (データベースからの) 多くの質問がある HTML フォームを作成しました。次に、ユーザーはそれらの質問に答えます。ユーザーがタブを変更するたびに、JavaScript が保存を作成します。問題は、タブが変更されるたびにすべての質問をループする必要があり、フォームが毎回約 5 秒間フリーズすることです。

バックグラウンドで保存機能を実行する方法を探していました。どうやらバックグラウンドで何かを実行する実際の方法はなく、多くの人が使用を推奨していますsetTimeout();。たとえば、バックグラウンドで実行されている js 関数のグループを取得する方法

setTimeout(saveFunction, 2000);しかし、これらの例のいずれも、そのようなものを使用しても問題が解決しないことを説明したり、考慮したりしていません。この場合、2 秒だけ延期されます。

この問題を解決する方法はありますか?

4

7 に答える 7

7

Web ワーカーを使用できます。ここでの古い回答のいくつかは、広くサポートされていないと言っています (これらの回答が書かれたときではなかったと思います) が、今日ではすべての主要なブラウザーでサポートされています

Web ワーカーを実行するには、組み込みWorkerクラスのインスタンスを作成する必要があります。コンストラクターは、バックグラウンドで実行するコードを含む JavaScript ファイルの URI である 1 つの引数を取ります。例えば:

let worker = new Worker("/path/to/script.js");

Web ワーカーは同一オリジン ポリシーの対象となるため、このようなパスを渡す場合、ターゲット スクリプトはそれを呼び出すページと同じドメインにある必要があります。

このためだけに新しい Javascript ファイルを作成したくない場合は、データ URI を使用することもできます。

let worker = new Worker(
    `data:text/javascript,
    //Enter Javascript code here
    `
);

オリジン ポリシーが同じであるため、データ URI から AJAX 要求を送信することはできません。そのため、Web ワーカーで AJAX 要求を送信する必要がある場合は、別の Javascript ファイルを使用する必要があります。

指定したコード (別のファイルまたはデータ URI のいずれか) は、Workerコンストラクターを呼び出すとすぐに実行されます。

残念ながら、Web ワーカーは外部の Javascript 変数、関数、クラス、DOM のいずれにもアクセスできませんが、postMessageメソッドとonmessageイベントを使用することでこれを回避できます。外側のコードでは、これらは worker オブジェクト (worker上記の例) のメンバーであり、worker の内側では、これらはグローバル コンテキストのメンバーです (したがってthis、前に何もない を使用するか、そのように呼び出すことができます)。

postMessageonmessage両方の方法で動作するため、がworker.postMessage外部コードで呼び出されるonmessageとワーカーで起動され、ワー​​カーでpostMessageが呼び出されるとworker.onmessage外部コードで起動されます。

postMessageこれは、渡したい変数です (ただし、配列を渡すことで複数の変数を渡すことができます)。残念ながら、関数と DOM 要素を渡すことはできません。オブジェクトを渡そうとすると、メソッドではなく属性のみが渡されます。

onmessageオブジェクトである 1 つの引数を取りMessageEventます。MessageEventオブジェクトには、のdata最初の引数を使用して送信されたデータを含む属性がありますpostMessage

これは、Web ワーカーを使用した例です。この例では、functionThatTakesLongTime1 つの引数を取り、その引数に応じて値を返す関数 がfunctionThatTakesLongTime(foo)あり、UI をフリーズせずに検索するために Web ワーカーを使用したいと考えてfooいます。外部コード内の変数はどこにありますか。

let worker = new Worker(
    `data:text/javascript,
    function functionThatTakesLongTime(someArgument){
        //There are obviously faster ways to do this, I made this function slow on purpose just for the example.
        for(let i = 0; i < 1000000000; i++){
            someArgument++;
        }
        return someArgument;
    }
    onmessage = function(event){    //This will be called when worker.postMessage is called in the outside code.
        let foo = event.data;    //Get the argument that was passed from the outside code, in this case foo.
        let result = functionThatTakesLongTime(foo);    //Find the result. This will take long time but it doesn't matter since it's called in the worker.
        postMessage(result);    //Send the result to the outside code.
    };
    `
);

worker.onmessage = function(event){    //Get the result from the worker. This code will be called when postMessage is called in the worker.
    alert("The result is " + event.data);
}

worker.postMessage(foo);    //Send foo to the worker (here foo is just some variable that was defined somewhere previously).
于 2020-06-12T17:00:44.910 に答える
0

DOM にアクセスする必要があるために Web ワーカーを使用できない場合は、async functionsを使用することもできます。アイデアはrefreshUI、UI を更新する非同期関数を作成し、その関数を関数内で定期的に呼び出すことです。これには長い時間がかかります。

関数は次のrefreshUIようになります。

async function refreshUI(){
    await new Promise(r => setTimeout(r, 0));
}

一般にawait new Promise(r => setTimeout(r, ms));、非同期関数を配置すると、その行の前のすべてのコードが実行されms、UI をフリーズせずに数ミリ秒待機してから、その行の後のコードの実行が続行されます。詳細については、この回答を参照してください。

上記の関数は、続行する前に UI をフリーズせずにrefreshUI0 ミリ秒待機することを除いて、同じことを行います。これは、実際には、UI を更新してから続行することを意味します。

この関数を使用して UI を頻繁に更新すると、ユーザーは UI がフリーズしていることに気づきません。

ただし、UI の更新には時間がかかります (1 回だけ行うと気付くには十分な時間ではありませんが、長い for ループの反復ごとに行うと気付くには十分な時間です)。そのため、UI をフリーズせずに関数をできるだけ速く実行したい場合は、UI を頻繁に更新しないようにする必要があります。そのため、UI がフリーズしないように頻繁に UI を更新することと、コードが大幅に遅くなるほど頻繁に更新しないことのバランスを見つける必要があります。私の使用例では、UI を 20 ミリ秒ごとに更新するのがバランスが良いことがわかりました。

refreshUI呼び出し頻度に関係なく、20 ミリ秒ごとに 1 回だけ UI を更新するように上記の関数を書き直すことができperformance.now()ます (必要に応じて、独自のコードでその数を調整できます)。

let startTime = performance.now();
async function refreshUI(){
    if(performance.now() > startTime + 20){    //You can change the 20 to how often you want to refresh the UI in milliseconds
        startTime = performance.now();
        await new Promise(r => setTimeout(r, 0));
    }
}

これを行えば、 を頻繁に呼び出すことを心配する必要はありませんrefreshUI(ただし、十分に頻繁に呼び出す必要があります)。

は非同期関数であるためrefreshUI、使用して呼び出す必要があり、それを呼び出すawait refreshUI()関数も非同期関数である必要があります。

これは、他の回答の最後にある例と同じことを行う例ですが、代わりにこのメソッドを使用しています:

let startTime = performance.now();
async function refreshUI(){
    if(performance.now() > startTime + 20){    //You can change the 20 to how often you want to refresh the UI in milliseconds
        startTime = performance.now();
        await new Promise(r => setTimeout(r, 0));
    }
}

async function functionThatTakesLongTime(someArgument){
    //There are obviously faster ways to do this, I made this function slow on purpose just for the example.
    for(let i = 0; i < 1000000000; i++){
        someArgument++;
        await refreshUI();    //Refresh the UI if needed
    }
    return someArgument;
}

alert("The result is " + await functionThatTakesLongTime(3));
于 2020-08-29T10:44:31.047 に答える