Как избежать расы в javascript - PullRequest
20 голосов
/ 03 декабря 2008

Вот сценарий:

Моим пользователям представлена ​​сетка, в основном упрощенная версия электронной таблицы. В каждой строке сетки есть текстовые поля. Когда они изменяют значение в текстовом поле, я выполняю проверку их ввода, обновляю коллекцию, которая управляет сеткой, и перерисовываю промежуточные итоги на странице. Все это обрабатывается событием OnChange каждого текстового поля.

Когда они нажимают кнопку «Сохранить», я использую событие OnClick кнопки, чтобы выполнить окончательную проверку сумм, а затем отправляю весь свой вклад в веб-службу, сохраняя ее.

По крайней мере, так будет, если они перейдут через форму к кнопке «Отправить».

Проблема в том, что если они вводят значение, а затем сразу же нажимают кнопку сохранения, SaveForm () начинает выполняться до завершения UserInputChanged () - условие гонки. Мой код не использует setTimeout, но я использую его для имитации медленного кода проверки UserInputChanged:

 <!-- snip -->
 <script>
    var amount = null;
    var currentControl = null;

    function UserInputChanged(control) {
        currentControl = control;
        // use setTimeout to simulate slow validation code (production code does not use setTimeout)
        setTimeout("ValidateAmount()", 100); 
    }

    function SaveForm() {
        // call web service to save value
        document.getElementById("SavedAmount").innerHTML = amount;
    }

    function ValidateAmount() {
        // various validationey functions here
        amount = currentControl.value; // save value to collection
        document.getElementById("Subtotal").innerHTML = amount; // update subtotals

    }
</script>
<!-- snip -->
Amount: <input type="text" id="UserInputValue" onchange="UserInputChanged(this);" /> <br />
Subtotal: <span id="Subtotal"></span> <br />
<input type="button" onclick="SaveForm();" value="Save" /> <br /><br />
Saved amount: <span id="SavedAmount"></span>
<!-- snip -->

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

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

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

Ответы [ 7 ]

21 голосов
/ 03 декабря 2008

Используйте семафор (назовем его StillNeedsValidating). если функция SaveForm видит, что семафор StillNeedsValidating работает, пусть активирует второй собственный семафор (который я назову здесь FormNeedsSaving) и вернусь. Когда функция проверки завершается, если семафор FormNeedsSaving запущен, она сама вызывает функцию SaveForm.

В янк-коде;

function UserInputChanged(control) {
    StillNeedsValidating = true;
    // do validation
    StillNeedsValidating = false;
    if (FormNeedsSaving) saveForm(); 
}

function SaveForm() {
    if (StillNeedsValidating) { FormNeedsSaving=true; return; }
    // call web service to save value
    FormNeedsSaving = false;
}
9 голосов
/ 03 декабря 2008

Отключить кнопку сохранения во время проверки. Установите его в отключенное состояние, как в первую очередь при проверке, и снова включите его по окончании.

, например

function UserInputChanged(control) {
    // --> disable button here --< 
    currentControl = control;
    // use setTimeout to simulate slow validation code (production code does not use setTimeout)
    setTimeout("ValidateAmount()", 100); 
}

и

function ValidateAmount() {
    // various validationey functions here
    amount = currentControl.value; // save value to collection
    document.getElementById("Subtotal").innerHTML = amount; // update subtotals
    // --> enable button here if validation passes --<
}

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

4 голосов
/ 03 декабря 2008

Я думаю, что тайм-аут вызывает вашу проблему ... если это будет простой код (без асинхронных вызовов AJAX, тайм-аутов и т. Д.), То я не думаю, что SaveForm будет выполнен до завершения UserInputChanged.

2 голосов
/ 03 декабря 2008

Возможно, лучше всего использовать семафор или мьютекс, но вместо занятого цикла просто используйте setTimeout() для имитации спящего потока. Как это:

busy = false;

function UserInputChanged(control) {
    busy = true;
    currentControl = control;
    // use setTimeout to simulate slow validation code (production code does not use setTimeout)
    setTimeout("ValidateAmount()", 100); 
}

function SaveForm() {
    if(busy) 
    {
       setTimeout("SaveForm()", 10);
       return;
    }

    // call web service to save value
    document.getElementById("SavedAmount").innerHTML = amount;
}

function ValidateAmount() {
    // various validationey functions here
    amount = currentControl.value; // save value to collection
    document.getElementById("Subtotal").innerHTML = amount; // update subtotals
    busy = false;
}
0 голосов
/ 17 сентября 2010

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

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

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

Вот почему я создал библиотеку proto-q: http://code.google.com/p/proto-q/

Проверьте, если вы делаете много такого рода работы.

0 голосов
/ 03 декабря 2008

У вас нет состояния гонки, условия гонки не могут возникать в javascript, так как javascript является однопоточным, поэтому 2 потока не могут мешать друг другу.

Пример, который вы приводите, не очень хороший пример. Вызов setTimeout поместит вызванную функцию в очередь в движке javascript и запустит ее позже. Если в этот момент вы нажмете кнопку сохранения, функция setTimeout не будет вызываться, пока ПОСЛЕ сохранения полностью не завершено.

Вероятно, в вашем javascript происходит то, что событие onClick вызывается механизмом javascript до вызова события onChange.

В качестве подсказки помните, что javascript является однопоточным, если только вы не используете отладчик javascript (firebug, отладчик microsoft screipt). Эти программы перехватывают поток и приостанавливают его. С этого момента другие потоки (через события, вызовы setTimeout или обработчики XMLHttp) могут запускаться, создавая впечатление, что javascript может запускать несколько потоков одновременно.

0 голосов
/ 03 декабря 2008

Вы можете настроить повторяющуюся функцию, которая отслеживает состояние всей сетки и вызывает событие, которое указывает, является ли вся сетка действительной или нет.

Ваша кнопка «отправить форму» включит или отключит себя в зависимости от этого статуса.

О, сейчас я вижу похожий ответ - это тоже работает, конечно.

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