Как реализовать блокировку в JavaScript

83

Как можно lockбыло реализовать что-то, эквивалентное C #, в JavaScript?

Итак, чтобы объяснить, что я думаю, простой вариант использования:

Пользователь нажимает кнопку B. Bвызывает событие onclick. Если Bв , event-stateожидает событие для Bбыть в ready-stateперед его распространением. Если Bесть ready-state, Bзаблокировано и установлено в event-state, то событие распространяется. Когда распространение события завершено, Bустанавливается значение ready-state.

Я мог видеть, как можно сделать что-то близкое к этому, просто добавив и удалив класс ready-stateс помощью кнопки. Однако проблема заключается в том, что пользователь может дважды щелкнуть кнопку подряд быстрее, чем может быть установлена ​​переменная, поэтому в некоторых случаях эта попытка блокировки не удастся.

Кто-нибудь знает, как реализовать блокировку, которая не откажет в JavaScript?

умный пещерный человек
источник
1
Приветствуются ответы, учитывающие параллельное выполнение JS (например, как в IE9).
OrangeDog
@smart Итак, вы хотите временно приостановить распространение текущего события, пока распространение предыдущего события не будет завершено. Я не думаю, что это можно сделать. Вы можете сделать это: отменить событие, а затем запустить другое, когда предыдущее событие закончило распространение.
Шиме Видас
@OrangeDog - я только слышал, что IE9 пытается использовать выделенное ядро ​​для компиляции, ничего о параллельном выполнении, можете ли вы сослаться на источник?
Brandon
1
@Brandon - это может означать, как вы предлагаете, параллельно рендереру, а не параллельно самому себе.
OrangeDog
1
Связанный: stackoverflow.com/questions/6266868/…
Дэвид Мердок

Ответы:

85

Блокировка - это сомнительная идея в JS, которая предназначена для беспоточной работы и не требует защиты параллелизма. Вы хотите объединить вызовы с отложенным исполнением. Шаблон, который я следую для этого, - это использование обратных вызовов. Что-то вроде этого:

var functionLock = false;
var functionCallbacks = [];
var lockingFunction = function (callback) {
    if (functionLock) {
        functionCallbacks.push(callback);
    } else {
        $.longRunning(function(response) {
             while(functionCallbacks.length){
                 var thisCallback = functionCallbacks.pop();
                 thisCallback(response);
             }
        });
    }
}

Вы также можете реализовать это с помощью прослушивателей событий DOM или решения pubsub.

ДжошРиверс
источник
18
Не могли бы вы предоставить справочную документацию, обосновывающую ваше заявление о том, что «JS предназначен для работы без потоков и не требует защиты параллелизма»? Я хотел бы узнать об этом подробнее.
smartcaveman 05
2
+1 - Учитывая, что Node.js (веб-сервер JavaScript) был построен для использования преимуществ механизма одиночного потока и обратного вызова JavaScript, я согласен, что вам не нужно беспокоиться о блокировке свойства, поскольку не будет условия гонки.
Fenton
4
Из какой библиотеки $ .longRunning? Его нет в (текущем) jQuery.
Quantum7
3
когда должна быть установлена ​​функция functionLock?
Элтон Гарсия де Сантана
11
«JS, который предназначен для работы без потоков и не требует параллелизма» сомнительно. JS не использует вытесняющий параллелизм, но позволяет использовать кооперативный параллелизм (обратный вызов или на основе async / await). У вас определенно могут быть условия гонки при их использовании и, возможно, вы захотите использовать абстракцию мьютекса, чтобы избежать их.
ysdx
32

JavaScript, за очень немногими исключениями ( XMLHttpRequest onreadystatechangeобработчики в некоторых версиях Firefox), является параллельным циклом событий . Таким образом, вам не нужно беспокоиться о блокировке в этом случае.

В JavaScript есть модель параллелизма, основанная на «цикле событий». Эта модель сильно отличается от модели на других языках, таких как C или Java.

...

Среда выполнения JavaScript содержит очередь сообщений, которая представляет собой список сообщений для обработки. Каждому сообщению соответствует функция. Когда стек пуст, сообщение извлекается из очереди и обрабатывается. Обработка состоит из вызова связанной функции (и, таким образом, создания начального кадра стека). Обработка сообщения заканчивается, когда стек снова становится пустым.

...

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

Обратной стороной этой модели является то, что, если сообщение занимает слишком много времени для завершения, веб-приложение не может обрабатывать действия пользователя, такие как щелчок или прокрутка. Браузер смягчает это с помощью диалогового окна «Сценарий слишком долго запускается». Хорошая практика - сократить время обработки сообщения и, если возможно, разделить одно сообщение на несколько.

Дополнительные ссылки на параллелизм цикла событий см. В E

Майк Сэмюэл
источник
А с веб-воркерами (не потоками пользовательского интерфейса) в javascript каждый объект клонирует перед отправкой другому рабочему (потоку) или потоку пользовательского интерфейса, тогда объект имеет разные экземпляры в разных потоках, и каждый поток имеет цикл событий, и, наконец, я согласен с ответом Майка как полезный ответ. Спасибо, Майк.
Ehsan Mohammadi
11

У меня был успех мьютекс-обещание .

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

Тим Скотт
источник
Спасибо, очень нужная вещь.
Дмитрий Полянин
6

Замки - это концепция, необходимая в многопоточной системе. Даже с рабочими потоками сообщения отправляются между рабочими по значению, поэтому блокировка не требуется.

Я подозреваю, что вам нужно просто установить семафор (систему флагов) между вашими кнопками.

Джеймс Вестгейт
источник
1
есть ли у вас такие примеры или ресурсы для «системы семафоров / пометок», реализованных на JavaScript?
smartcaveman 08
4
PLenty прямо здесь, на SO, т.е. stackoverflow.com/questions/4194346/…
Джеймс Вестгейт,
Джеймс: Я не вижу примеров семафоров в опубликованной вами ссылке. Мне нужно предотвратить вызовы внешнего сервера, потому что они разрешают только один запрос в секунду, поэтому мне нужен пример пометки, который предотвратил бы вызов до тех пор, пока истекшее время не пройдет внутри моего цикла for next без пропуска запроса для каждого из циклов.
Клаус
Привет, @Claus. На ум приходят две вещи: в зависимости от вашего стека вы можете просто использовать библиотеку, такую ​​как Axios, которая, кажется, поддерживает плагин ограничения скорости, или, возможно, вы можете создать веб-мастера, который ограничивает скорость запросов на основе домена.
Джеймс Вестгейт
Спасибо за ответ. Я решил это, вызвав событие щелчка в конце обработки в сочетании с setInterval. Установив интервал в 1000 миллисекунд, я теперь подчиняюсь ограничениям хост-сервера на запросы, и он больше не завершает вызов Ajax до того, как будет возвращен ответ.
Клаус
2

Почему бы вам не отключить кнопку и не включить ее после завершения мероприятия?

<input type="button" id="xx" onclick="checkEnableSubmit('true');yourFunction();">

<script type="text/javascript">

function checkEnableSubmit(status) {  
  document.getElementById("xx").disabled = status;
}

function yourFunction(){

//add your functionality

checkEnableSubmit('false');
}

</script>

Удачного кодирования !!!

Равия
источник
0

Некоторое дополнение к ответу Джоша Ривера в соответствии с моим случаем;

var functionCallbacks = [];
    var functionLock = false;
    var getData = function (url, callback) {
                   if (functionLock) {
                        functionCallbacks.push(callback);
                   } else {
                       functionLock = true;
                       functionCallbacks.push(callback);
                        $.getJSON(url, function (data) {
                            while (functionCallbacks.length) {
                                var thisCallback = functionCallbacks.pop();
                                thisCallback(data);
                            }
                            functionLock = false;
                        });
                    }
                };

// Usage
getData("api/orders",function(data){
    barChart(data);
});
getData("api/orders",function(data){
  lineChart(data);
});

Будет только один вызов API, и эти две функции будут использовать одинаковый результат.

Кадир Джан
источник
0

Блокировки все еще используются в JS. По моему опыту, мне нужно было использовать только блокировки, чтобы предотвратить спам, нажимая на элементы, вызывающие AJAX. Если у вас есть загрузчик, настроенный для вызовов AJAX, это не требуется (равно как и отключение кнопки после нажатия). Но в любом случае вот что я использовал для блокировки:

var LOCK_INDEX = [];
function LockCallback(key, action, manual) {
    if (LOCK_INDEX[key])
        return;
    LOCK_INDEX[key] = true;
    action(function () { delete LOCK_INDEX[key] });
    if (!manual)
        delete LOCK_INDEX[key];
}

Применение:

Ручная разблокировка (обычно для XHR)

LockCallback('someKey',(delCallback) => { 
    //do stuff
    delCallback(); //Unlock method
}, true)

Автоматическая разблокировка

LockCallback('someKey',() => { 
    //do stuff
})
Бодох
источник
0

Вот простой механизм блокировки, реализованный через закрытие

const createLock = () => {

    let lockStatus = false

    const release = () => {
        lockStatus = false
    }

    const acuire = () => {
        if (lockStatus == true)
            return false
        lockStatus = true
        return true
    }
    
    return {
        lockStatus: lockStatus, 
        acuire: acuire,
        release: release,
    }
}

lock = createLock() // create a lock
lock.acuire() // acuired a lock

if (lock.acuire()){
  console.log("Was able to acuire");
} else {
  console.log("Was not to acuire"); // This will execute
}

lock.release() // now the lock is released

if(lock.acuire()){
  console.log("Was able to acuire"); // This will execute
} else {
  console.log("Was not to acuire"); 
}

lock.release() // Hey don't forget to release

user3841707
источник