web-dev-qa-db-ja.com

Javascriptのスレッドセーフ?

Save()という関数があります。この関数はページ上のすべての入力を収集し、サーバーに対してAJAX呼び出しを実行して、ユーザーの作業の状態を保存します。

現在、save()は、ユーザーが保存ボタンをクリックするか、サーバー上で最新の状態を維持する必要があるその他のアクションを実行したときに呼び出されます(たとえば、ページからドキュメントを生成します)。

ユーザーの作業を頻繁に自動保存する機能を追加しています。まず、自動保存とユーザー生成の保存が同時に実行されないようにします。したがって、次のコードがあります(私はほとんどのコードをカットしていますが、これは1:1ではありませんが、アイデアを伝えるには十分なはずです)。

var isSaving=false;
var timeoutId;
var timeoutInterval=300000;
function save(showMsg)
{
  //Don't save if we are already saving.
  if (isSaving)
  { 
     return;
  }
  isSaving=true;
  //disables the autoSave timer so if we are saving via some other method
  //we won't kick off the timer.
  disableAutoSave();

  if (showMsg) { //show a saving popup}
  params=CollectParams();
  PerformCallBack(params,endSave,endSaveError);

}
function endSave()
{  
    isSaving=false;
    //hides popup if it's visible

    //Turns auto saving back on so we save x milliseconds after the last save.
    enableAutoSave();

} 
function endSaveError()
{
   alert("Ooops");
   endSave();
}
function enableAutoSave()
{
    timeoutId=setTimeOut(function(){save(false);},timeoutInterval);
}
function disableAutoSave()
{
    cancelTimeOut(timeoutId);
}

私の質問は、このコードが安全かどうかです。主要なブラウザでは、一度に1つのスレッドしか実行できませんか?

私が持っていた1つの考えは、自動保存しているため、ユーザーが[保存]をクリックして応答がない場合はさらに悪いことです(これを処理するようにコードを変更する方法を知っています)。誰かがここで他の問題を見ますか?

34
JoshBerke

ブラウザのJavaScriptはシングルスレッドです。どの時点でも、1つの機能にしか参加できません。次の機能が入力される前に、機能は完了します。この動作は信頼できるので、save()関数を使用している場合は、現在の関数が終了するまで、この動作を再度入力することはありません。

これが時々混乱する(そしてまだ真実のままである)のは、非同期サーバー要求(またはsetTimeoutsまたはsetIntervals)がある場合です。これは、関数が インターリーブ であるように感じるためです。彼らはそうではありません。

あなたの場合、2つのsave()呼び出しが互いに重複することはありませんが、自動保存とユーザー保存が連続して発生する可能性があります。

少なくともx秒ごとに保存を実行したいだけの場合は、保存関数でsetIntervalを実行して、それを忘れることができます。 isSavingフラグは必要ないと思います。

あなたのコードはかなり単純化できると思います:

var intervalTime = 300000;
var intervalId = setInterval("save('my message')", intervalTime);
function save(showMsg)
{
  if (showMsg) { //show a saving popup}
  params=CollectParams();
  PerformCallBack(params, endSave, endSaveError);

  // You could even reset your interval now that you know we just saved.
  // Of course, you'll need to know it was a successful save.
  // Doing this will prevent the user clicking save only to have another
  // save bump them in the face right away because an interval comes up.
  clearInterval(intervalId);
  intervalId = setInterval("save('my message')", intervalTime);
}

function endSave()
{
    // no need for this method
    alert("I'm done saving!");
}

function endSaveError()
{
   alert("Ooops");
   endSave();
}
44
Jonathon Faust

すべての主要なブラウザは、ページ上で1つのjavascriptスレッドのみをサポートします( web worker を使用しない限り)。

ただし、XHRリクエストは非同期にすることができます。ただし、現在の保存要求が返されるまで保存機能を無効にしている限り、すべてが正常に機能するはずです。

私の唯一の提案は、自動保存が発生したときに何らかの方法でユーザーに示すことを確認することです(保存ボタンを無効にするなど)。

7
mikefrey

現在、すべての主要なブラウザーはシングルスレッドのJavaScriptを実行しているため(一部のブラウザーはこの手法をサポートしているため、 Webワーカー を使用しないでください!)、このアプローチは安全です。

一連の参照については、 JavaScriptはマルチスレッド化されていますか ?を参照してください。

2
Jeff Sternal

私には安全に見えます。 Javascriptはシングルスレッドです(Webworkerを使用している場合を除く)

あまり話題にはなりませんが、John Resigによるこの投稿では、JavaScriptのスレッド化とタイマーについて説明しています。 http://ejohn.org/blog/how-javascript-timers-work/

2
David Murdoch

私はあなたがそれを扱う方法があなたの状況に最適だと思います。フラグを使用することで、非同期呼び出しが重複しないことが保証されます。サーバーへの非同期呼び出しも処理する必要があり、重複を防ぐために何らかのフラグを使用しました。

他の人がすでに指摘しているように、JavaScriptはシングルスレッドですが、サーバーへのラウンドトリップ中に同じことを言うか、起こらないと予想する場合、非同期呼び出しは注意が必要です。

ただし、実際には自動保存を無効にする必要はないと思います。ユーザーが保存しているときに自動保存を実行しようとすると、saveメソッドは単に戻り、何も起こりません。一方、自動保存がアクティブになるたびに、自動保存を不必要に無効にしたり、再度有効にしたりします。 setIntervalに変更して、それを忘れることをお勧めします。

また、私はグローバル変数を最小化するための執着者です。私はおそらくあなたのコードを次のようにリファクタリングするでしょう:

var saveWork = (function() {
  var isSaving=false;
  var timeoutId;
  var timeoutInterval=300000;
  function endSave() {  
      isSaving=false;
      //hides popup if it's visible
  }
  function endSaveError() {
     alert("Ooops");
     endSave();
  }
  function _save(showMsg) {
    //Don't save if we are already saving.
    if (isSaving)
    { 
     return;
    }
    isSaving=true;

    if (showMsg) { //show a saving popup}
    params=CollectParams();
    PerformCallBack(params,endSave,endSaveError);
  }
  return {
    save: function(showMsg) { _save(showMsg); },
    enableAutoSave: function() {
      timeoutId=setInterval(function(){_save(false);},timeoutInterval);
    },
    disableAutoSave: function() {
      cancelTimeOut(timeoutId);
    }
  };
})();

もちろん、そのようにリファクタリングする必要はありませんが、私が言ったように、私はグローバルを最小化するのが好きです。重要なことは、保存するたびに自動保存を無効にしたり、再度有効にしたりしなくても、すべてが機能することです。

編集:enableAutoSaveから参照できるようにプライベート保存関数を作成する必要がありました

1
Bob