Избегание состояния javascript
Здесь сценарий:
Мои пользователи представляют сетку, в основном, урезанную версию электронной таблицы. В каждой строке сетки есть текстовые поля. Когда они меняют значение в текстовом поле, я выполняю проверку на их входе, обновляя коллекцию, которая управляет сеткой, и перерисовывая промежуточные итоги на странице. Все это обрабатывается событием 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мс - это магическое число между тем, выполняется ли код проверки до начала кода сохранения. Это может быть выше или ниже в зависимости от скорости компьютера пользователя.
Есть ли у кого-нибудь идеи, как справиться с этим условием? Сотрудник предложил использовать семафор при запуске кода проверки и цикл занятости в коде сохранения, чтобы дождаться разблокировки семафора, но я бы хотел избежать использования какого-либо цикла занятости в моем коде.
Ответы
Ответ 1
Используйте семафор (позвольте называть его StillNeedsValidating). если функция SaveForm увидит, что семафор StillNeedsValidating включен, активируйте второй собственный семафор (который я назову здесь FormNeedsSaving) и вернусь. Когда функция проверки завершается, если семафор FormNeedsSaving вставлен, он сам вызывает функцию SaveForm.
В jankcode;
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;
}
Ответ 2
Отключите кнопку сохранения во время проверки.
Установите его как отключенное, как первое, что делает проверка, и снова включите его при завершении.
например.
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 и выполняете одну функцию проверки, но если у ваших пользователей нет сверхчеловеческих рефлексов, вам должно быть хорошо идти.
Ответ 3
Я думаю, что тайм-аут вызывает вашу проблему... если это будет простой код (без асинхронных вызовов AJAX, тайм-аутов и т.д.), то я не думаю, что SaveForm будет выполнен до завершения UserInputChanged.
Ответ 4
Семафор или мьютекс, вероятно, лучший способ пойти, но вместо цикла занятости просто используйте 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;
}
Ответ 5
У вас нет условия гонки, условия гонки не могут произойти в javascript, так как javascript однопоточный, поэтому 2 потока не могут мешать друг другу.
Пример, который вы даете, не очень хороший пример. Вызов setTimeout поместит вызываемую функцию в очередь в javascript-движке и запустит ее позже. Если в этот момент вы нажмете кнопку сохранения, функция setTimeout не будет вызываться до тех пор, пока ПОСЛЕ сохранения не будет полностью завершена.
Что, вероятно, происходит в вашем javascript, так это то, что событие onClick вызывается механизмом javascript перед вызовом onChange.
Как подсказка, имейте в виду, что javascript является однопоточным, если вы не используете отладчик javascript (firebug, microsoft screipt debugger). Эти программы перехватывают поток и приостанавливают его. С этого момента в других потоках (либо через события, и вызовы setTimeout, либо обработчики XMLHttp) могут выполняться, поэтому кажется, что javascript может одновременно запускать несколько потоков.
Ответ 6
Вы можете настроить повторяющуюся функцию, которая контролирует состояние всей сетки и вызывает событие, которое указывает, действительна ли вся сетка.
Кнопка "отправить форму" затем включит или отключит себя на основе этого состояния.
О, я вижу подобный ответ сейчас - это тоже работает, конечно.
Ответ 7
При работе с асинхронными источниками данных у вас могут быть условия гонки, так как поток процессов JavaScript продолжает выполнять директивы, которые могут зависеть от данных, которые еще не были возвращены из удаленного источника данных. Вот почему у нас есть функции обратного вызова.
В вашем примере вызов кода проверки должен иметь функцию обратного вызова, которая может что-то сделать, когда возвращается подтверждение.
Однако, когда вы делаете что-то со сложной логикой или пытаетесь устранить или улучшить существующую серию обратных вызовов, вы можете сходить с ума.
Вот почему я создал библиотеку прото-q: http://code.google.com/p/proto-q/
Проверьте это, если вы выполняете много такого рода работ.