Jak zaimplementować blokadę w JavaScript

83

Jak coś odpowiednika lockw C # można zaimplementować w JavaScript?

Aby więc wyjaśnić, o czym myślę, prosty przypadek użycia to:

Użytkownik klika przycisk B. Bpodnosi zdarzenie onclick. Jeśli Bjest w event-stateoczekiwania wydarzenie dla Bbyć w ready-stateprzed rozmnożeniowego. Jeśli Bjest w ready-state, Bjest zablokowany i ustawiony na event-state, to zdarzenie jest propagowane. Gdy propagacja zdarzenia zostanie zakończona, Bjest ustawiona na ready-state.

Widziałem, jak można zrobić coś podobnego, po prostu dodając i usuwając klasę ready-statez przycisku. Jednak problem polega na tym, że użytkownik może kliknąć przycisk dwa razy z rzędu szybciej niż można ustawić zmienną, więc ta próba zablokowania w niektórych okolicznościach zakończy się niepowodzeniem.

Czy ktoś wie, jak zaimplementować blokadę, która nie zawiedzie w JavaScript?

smartcaveman
źródło
1
Będą mile widziane odpowiedzi uwzględniające równoległe wykonanie JS (tj. Jak w IE9).
OrangeDog
@smart Więc chcesz tymczasowo wstrzymać propagację obecnego wydarzenia do czasu zakończenia propagacji poprzedniego. Nie sądzę, że można to zrobić. Możesz to zrobić: odrzucić zdarzenie, a następnie uruchomić następne, gdy poprzednie zdarzenie zakończy się propagować.
Šime Vidas
@OrangeDog - Słyszałem tylko, że IE9 próbuje użyć dedykowanego rdzenia do kompilacji, nic o równoległym wykonywaniu, czy możesz podać źródło?
Brandon,
1
@Brandon - To może oznaczać, jak sugerujesz, równolegle do mechanizmu renderującego, a nie równolegle do siebie.
OrangeDog
1
Związane z: stackoverflow.com/questions/6266868/…
David Murdoch

Odpowiedzi:

85

Lock to wątpliwy pomysł w JS, który ma być bezgwintowy i nie wymaga ochrony współbieżności. Chcesz połączyć wezwania do odroczonego wykonania. Wzorzec, który podążam za tym, to użycie wywołań zwrotnych. Coś takiego:

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);
             }
        });
    }
}

Można to również zaimplementować za pomocą detektorów zdarzeń DOM lub rozwiązania pubsub.

JoshRivers
źródło
18
czy mógłbyś podać dokumentację referencyjną uzasadniającą twoje stwierdzenie, że „JS ma być bez wątków i nie potrzebuje ochrony współbieżności”? Chciałbym przeczytać więcej na ten temat.
smartcaveman
2
+1 - Biorąc pod uwagę, że Node.js (serwer WWW JavaScript) został zbudowany w celu wykorzystania samotnego wątku i mechanizmu wywołania zwrotnego JavaScript, zgadzam się, że nie musisz się martwić o blokowanie właściwości, ponieważ nie będzie to warunek wyścigu.
Fenton,
4
Z jakiej biblioteki pochodzi $ .longRunning? Nie ma go w (bieżącym) jQuery.
Quantum7
3
kiedy należy ustawić functionLock?
Elton Garcia de Santana
11
„JS, który ma być bezgwintowy i niewymagający współbieżności” jest wątpliwy. JS nie używa współbieżności z wywłaszczaniem, ale pozwala na użycie współbieżności kooperacyjnej (na podstawie wywołania zwrotnego lub async / await). Zdecydowanie możesz mieć warunki wyścigu, gdy ich używasz i możesz chcieć użyć abstrakcji muteksu, aby ich uniknąć.
ysdx
32

JavaScript, z nielicznymi wyjątkami ( XMLHttpRequest onreadystatechangeprogramy obsługi w niektórych wersjach Firefoksa) , jest współbieżna w pętli zdarzeń . Nie musisz więc martwić się o blokowanie w tym przypadku.

JavaScript ma model współbieżności oparty na „pętli zdarzeń”. Ten model jest zupełnie inny niż model w innych językach, takich jak C czy Java.

...

Środowisko wykonawcze JavaScript zawiera kolejkę komunikatów, która jest listą komunikatów do przetworzenia. Do każdej wiadomości przypisana jest funkcja. Gdy stos jest pusty, wiadomość jest usuwana z kolejki i przetwarzana. Przetwarzanie polega na wywołaniu skojarzonej funkcji (i utworzeniu w ten sposób początkowej ramki stosu). Przetwarzanie komunikatu kończy się, gdy stos ponownie staje się pusty.

...

Każda wiadomość jest przetwarzana w całości przed przetworzeniem jakiejkolwiek innej wiadomości. Daje to kilka fajnych właściwości, jeśli chodzi o rozumowanie na temat twojego programu, w tym fakt, że za każdym razem, gdy funkcja jest uruchomiona, nie może zostać usunięta i będzie działać całkowicie przed uruchomieniem jakiegokolwiek innego kodu (i może modyfikować dane, którymi operuje funkcja). Różni się to od C, na przykład, gdy funkcja działa w wątku, można ją zatrzymać w dowolnym momencie, aby uruchomić inny kod w innym wątku.

Wadą tego modelu jest to, że jeśli wiadomość trwa zbyt długo, aplikacja internetowa nie jest w stanie przetwarzać interakcji użytkownika, takich jak kliknięcie lub przewijanie. Przeglądarka łagodzi ten problem, wyświetlając okno dialogowe „skrypt trwa zbyt długo”. Dobrą praktyką do naśladowania jest skrócenie przetwarzania wiadomości i, jeśli to możliwe, ograniczenie jednej wiadomości do kilku.

Aby uzyskać więcej linków na temat współbieżności pętli zdarzeń, zobacz E

Mike Samuel
źródło
A w przypadku elementów roboczych (wątków innych niż UI) w javascript, każdy obiekt klonuje się przed wysłaniem do innego procesu roboczego (wątku) lub wątku interfejsu użytkownika, a następnie obiekt ma różne instancje w różnych wątkach, a każdy wątek posiada pętlę zdarzeń i ostatecznie zgadzam się z odpowiedzią Mike'a: pomocna odpowiedź. Dzięki Mike.
Ehsan Mohammadi
11

Miałem obietnicę sukcesu .

Zgadzam się z innymi odpowiedziami, że możesz nie potrzebować blokowania w swoim przypadku. Ale nie jest prawdą, że nigdy nie trzeba blokować w JavaScript. Potrzebujesz wzajemnej wyłączności podczas uzyskiwania dostępu do zasobów zewnętrznych, które nie obsługują współbieżności.

Tim Scott
źródło
Dzięki, bardzo potrzebna rzecz.
Dmitrij Polyanin
6

Zamki to koncepcja wymagana w systemie wielowątkowym. Nawet w przypadku wątków roboczych komunikaty są wysyłane według wartości między procesami roboczymi, dzięki czemu blokowanie nie jest konieczne.

Podejrzewam, że musisz po prostu ustawić semafor (system flagowania) między przyciskami.

James Westgate
źródło
1
czy masz jakieś przykłady lub zasoby dotyczące „systemu semaforów / flag” zaimplementowanego w JavaScript?
smartcaveman
4
Mnóstwo tu na SO tj stackoverflow.com/questions/4194346/...
James Westgate
James Nie widzę żadnych przykładów semaforów w podanym przez Ciebie łączu. Muszę zapobiegać wywołaniom do serwera zewnętrznego, ponieważ zezwalają one tylko na jedno żądanie na sekundę, więc potrzebuję przykładu flagowania, który uniemożliwi wywołanie, dopóki czas, który upłynął, nie minie w mojej pętli for next bez pomijania żądania dla każdej z pętli.
Mikołaj
Cześć @Claus. Przychodzą na myśl dwie rzeczy, w zależności od stosu, możesz po prostu użyć biblioteki, takiej jak Axios, która wydaje się obsługiwać wtyczkę limitu szybkości, lub może możesz stworzyć webworker, który ogranicza żądania w oparciu o domenę.
James Westgate
Dzięki za odpowiedzi. Rozwiązałem to, wywołując zdarzenie click pod koniec przetwarzania w połączeniu z setInterval. Ustawiam interwał na 1000 milisekund. Przestrzegam teraz limitu serwera hosta dotyczącego żądań i nie kończy on już wywołania Ajax przed zwróceniem odpowiedzi.
Mikołaj
2

Dlaczego nie wyłączysz przycisku i nie włączysz go po zakończeniu wydarzenia?

<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>

Miłego kodowania !!!

Ravia
źródło
0

Jakiś dodatek do odpowiedzi JoshRivera w moim przypadku;

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);
});

Będzie tylko jedno wywołanie interfejsu API i te dwie funkcje będą zużywać ten sam wynik.

Kadir Can
źródło
0

Zamki nadal mają zastosowanie w JS. Z mojego doświadczenia wynika, że ​​potrzebowałem tylko blokad, aby zapobiec klikaniu spamu na elementach wykonujących połączenia AJAX. Jeśli masz program ładujący skonfigurowany do połączeń AJAX, nie jest to wymagane (a także wyłączanie przycisku po kliknięciu). Ale tak czy inaczej, oto czego użyłem do zablokowania:

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];
}

Stosowanie:

Ręczne odblokowanie (zwykle dla XHR)

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

Automatyczne odblokowanie

LockCallback('someKey',() => { 
    //do stuff
})
Bodokh
źródło
0

Oto prosty mechanizm blokujący, realizowany poprzez zamknięcie

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
źródło