Безопасность потоков в Javascript? - PullRequest
34 голосов
/ 12 февраля 2010

У меня есть функция 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);
}

Мой вопрос: безопасен ли этот код? Разрешают ли основные браузеры одновременно выполнять только один поток?

Одна мысль, которая у меня возникла, заключается в том, что пользователю будет хуже нажимать кнопку сохранения и не получать ответ, потому что мы автосохраняем (и я знаю, как изменить код, чтобы справиться с этим). Кто-нибудь видит какие-либо другие проблемы здесь?

Ответы [ 5 ]

44 голосов
/ 12 февраля 2010

JavaScript в браузерах однопоточный. Вы будете когда-либо только в одной функции в любой момент времени. Функции будут завершены до ввода следующего. Вы можете рассчитывать на это поведение, поэтому, если вы находитесь в функции save(), вы никогда не будете вводить ее снова, пока не закончится текущая.

Где это иногда сбивает с толку (и все же остается верным), когда у вас есть асинхронные запросы к серверу (или setTimeouts или setIntervals), потому что тогда кажется, что ваши функции чередуются . Это не так.

В вашем случае, хотя два 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();
}
7 голосов
/ 12 февраля 2010

Все основные браузеры поддерживают только одну нить JavaScript (если вы не используете веб-работников ) на странице.

XHR-запросы могут быть асинхронными. Но до тех пор, пока вы отключите возможность сохранения до тех пор, пока не вернется текущий запрос на сохранение, все должно работать нормально.

Мое единственное предложение - убедиться, что вы как-то указываете пользователю, когда происходит автосохранение (отключите кнопку сохранения и т. Д.).

2 голосов
/ 12 февраля 2010

Выглядит безопасно для меня. Javascript является однопоточным (если вы не используете веб-работников)

Это не совсем по теме, но этот пост Джона Резига описывает потоки javascript и таймеры: http://ejohn.org/blog/how-javascript-timers-work/

2 голосов
/ 12 февраля 2010

Все основные браузеры в настоящее время выполняют однопоточное выполнение JavaScript (просто не используйте веб-работников , поскольку некоторые браузеры поддерживают эту технику!), Поэтому этот подход безопасен.

Несколько ссылок см. Является ли JavaScript многопоточным ?

1 голос
/ 13 февраля 2010

Я думаю, то, как вы справляетесь с этим, лучше всего подходит для вашей ситуации. Используя этот флаг, вы гарантируете, что асинхронные вызовы не перекрываются. Я также имел дело с асинхронными вызовами к серверу, а также использовал какой-то флаг, чтобы предотвратить наложение.

Как уже отмечали другие, JavaScript является однопоточным, но асинхронные вызовы могут быть сложными, если вы ожидаете, что вещи скажут то же самое или не произойдут во время обратной передачи на сервер.

Одна вещь, однако, в том, что я не думаю, что вам действительно нужно отключить автосохранение. Если автоматическое сохранение пытается произойти, когда пользователь сохраняет, то метод сохранения просто вернется, и ничего не произойдет. С другой стороны, вы без необходимости отключаете и снова включаете автосохранение каждый раз, когда автосохранение активируется. Я бы рекомендовал перейти на 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

...