So implementieren Sie eine Sperre in JavaScript

83

Wie könnte etwas, das lockC # entspricht, in JavaScript implementiert werden?

Um zu erklären, was ich für einen einfachen Anwendungsfall halte, ist:

Benutzer klickt auf die Schaltfläche B. Blöst ein Onclick-Ereignis aus. Wenn Bin ist event-statedie Veranstaltung wartet , Bum in ready-statevor ausbreitet. Wenn Bin ready-state, Bgesperrt und auf gesetzt ist event-state, wird das Ereignis weitergegeben. Wenn die Weitergabe des Ereignisses abgeschlossen ist, Bwird auf gesetzt ready-state.

Ich konnte sehen, wie etwas in der Nähe davon getan werden konnte, indem ich einfach die Klasse hinzufügte und ready-statevon der Schaltfläche entfernte. Das Problem ist jedoch, dass ein Benutzer zweimal hintereinander schneller auf eine Schaltfläche klicken kann, als die Variable festgelegt werden kann, sodass dieser Versuch einer Sperre unter bestimmten Umständen fehlschlägt.

Weiß jemand, wie man eine Sperre implementiert, die in JavaScript nicht fehlschlägt?

Smartcaveman
quelle
1
Antworten, die die parallele JS-Ausführung berücksichtigen (dh wie in IE9), sind willkommen.
OrangeDog
@smart Sie möchten also die aktuelle Ereignisausbreitung vorübergehend anhalten, bis die Weitergabe eines vorherigen Ereignisses abgeschlossen ist. Ich glaube nicht, dass das möglich ist. Sie können dies tun: Verwerfen Sie das Ereignis und lösen Sie ein anderes aus, wenn das vorherige Ereignis die Weitergabe beendet hat.
Šime Vidas
@OrangeDog - Ich habe nur gehört, dass IE9 versucht, einen dedizierten Kern für die Kompilierung zu verwenden. Nichts über die parallele Ausführung. Können Sie eine Quelle angeben?
Brandon
1
@Brandon - Das könnte bedeuten, wie Sie vorschlagen, parallel zum Renderer und nicht parallel zu sich selbst.
OrangeDog
1
Siehe auch
David Murdoch

Antworten:

84

Lock ist eine fragwürdige Idee in JS, die threadlos sein soll und keinen Parallelitätsschutz benötigt. Sie möchten Aufrufe zur verzögerten Ausführung kombinieren. Das Muster, dem ich hier folge, ist die Verwendung von Rückrufen. Etwas wie das:

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

Sie können dies auch mit DOM-Ereignis-Listenern oder einer Pubsub-Lösung implementieren.

JoshRivers
quelle
18
Könnten Sie bitte eine Referenzdokumentation vorlegen, die Ihre Aussage begründet, dass "JS threadlos sein soll und keinen Parallelitätsschutz benötigt"? Ich würde gerne mehr darüber lesen.
Smartcaveman
2
+1 - Da Node.js (JavaScript-Webserver) so erstellt wurde, dass es den Lone-Thread- und Callback-Mechanismus von JavaScript nutzt, würde ich zustimmen, dass Sie sich keine Gedanken über das Sperren einer Eigenschaft machen müssen, da keine Race-Bedingung vorliegt.
Fenton
4
Aus welcher Bibliothek stammt $ .longRunning? Es ist nicht in (aktueller) jQuery.
Quantum7
3
wann soll functionLock gesetzt werden?
Elton Garcia de Santana
9
"JS, das threadlos sein soll und keine Parallelität benötigt" ist zweifelhaft. JS verwendet keine präemptive Parallelität, ermöglicht jedoch die Verwendung der kooperativen Parallelität (Rückruf oder asynchron / wartenbasiert). Sie können definitiv Rennbedingungen haben, wenn Sie diese verwenden, und möchten möglicherweise eine Mutex-Abstraktion verwenden, um sie zu vermeiden.
Ysdx
32

JavaScript ist mit wenigen Ausnahmen ( XMLHttpRequest onreadystatechangeHandler in einigen Versionen von Firefox) gleichzeitig eine Ereignisschleife . In diesem Fall müssen Sie sich also keine Gedanken über das Sperren machen.

JavaScript hat ein Parallelitätsmodell, das auf einer "Ereignisschleife" basiert. Dieses Modell unterscheidet sich erheblich von dem Modell in anderen Sprachen wie C oder Java.

...

Eine JavaScript-Laufzeit enthält eine Nachrichtenwarteschlange, bei der es sich um eine Liste der zu verarbeitenden Nachrichten handelt. Jeder Nachricht ist eine Funktion zugeordnet. Wenn der Stapel leer ist, wird eine Nachricht aus der Warteschlange genommen und verarbeitet. Die Verarbeitung besteht aus dem Aufrufen der zugehörigen Funktion (und dem Erstellen eines anfänglichen Stapelrahmens). Die Nachrichtenverarbeitung endet, wenn der Stapel wieder leer wird.

...

Jede Nachricht wird vollständig verarbeitet, bevor eine andere Nachricht verarbeitet wird. Dies bietet einige nette Eigenschaften, wenn Sie über Ihr Programm nachdenken, einschließlich der Tatsache, dass eine Funktion, wenn sie ausgeführt wird, nicht vorbelegt werden kann und vollständig ausgeführt wird, bevor ein anderer Code ausgeführt wird (und Daten ändern kann, die von der Funktion bearbeitet werden). Dies unterscheidet sich beispielsweise von C. Wenn eine Funktion in einem Thread ausgeführt wird, kann sie jederzeit gestoppt werden, um anderen Code in einem anderen Thread auszuführen.

Ein Nachteil dieses Modells ist, dass die Webanwendung Benutzerinteraktionen wie Klicken oder Scrollen nicht verarbeiten kann, wenn das Abschließen einer Nachricht zu lange dauert. Der Browser mildert dies mit dem Dialogfeld "Die Ausführung eines Skripts dauert zu lange". Es empfiehlt sich, die Nachrichtenverarbeitung zu verkürzen und eine Nachricht nach Möglichkeit in mehrere Nachrichten zu zerlegen.

Weitere Links zur Ereignisschleifen-Parallelität finden Sie unter E.

Mike Samuel
quelle
Und mit Web Worker (nicht UI - Threads) in Javascript, jedes Objekt Klont vor Senden Sie einem anderen Arbeiter (Thread) oder UI - Thread dann das Objekt verschiedene Instanzen in verschiedenen Threads und jeden Thread hat Ereignisschleife im Besitz und schließlich mit Mike Antwort , wie ich einverstanden eine hilfreiche Antwort. Danke Mike.
Ehsan Mohammadi
10

Ich hatte Erfolg Mutex-Versprechen .

Ich stimme anderen Antworten zu, dass Sie in Ihrem Fall möglicherweise keine Sperre benötigen. Aber es ist nicht wahr, dass man niemals Javascript sperren muss. Sie benötigen gegenseitige Ausschließlichkeit, wenn Sie auf externe Ressourcen zugreifen, die keine Parallelität verarbeiten.

Tim Scott
quelle
Danke sehr gebrauchtes Ding.
Dmitrij Polyanin
6

Schlösser sind ein Konzept, das in einem Multithread-System erforderlich ist. Selbst bei Worker-Threads werden Nachrichten nach Wert zwischen Workern gesendet, sodass das Sperren nicht erforderlich ist.

Ich vermute, Sie müssen nur ein Semaphor (Markierungssystem) zwischen Ihren Schaltflächen einstellen.

James Westgate
quelle
1
Haben Sie solche Beispiele oder Ressourcen für ein in JavaScript implementiertes 'Semaphor / Flagging-System'?
Smartcaveman
4
Viel hier auf SO, dh stackoverflow.com/questions/4194346/…
James Westgate
James Ich sehe keine Semaphorbeispiele in dem Link, den du gepostet hast. Ich muss Anrufe an einen externen Server verhindern, da diese nur eine Anforderung pro Sekunde zulassen. Daher benötige ich ein Kennzeichnungsbeispiel, das einen Anruf verhindert, bis die verstrichene Zeit in meiner for next-Schleife verstrichen ist, ohne eine Anforderung für jede der Schleifen zu überspringen.
Claus
Hallo @Claus. Je nach Stapel können Sie zwei Dinge beachten: Sie können einfach eine Bibliothek wie Axios verwenden, die anscheinend ein Ratenlimit-Plugin unterstützt, oder Sie können einen Webworker erstellen, der Ratenlimitanforderungen basierend auf der Domain erstellt.
James Westgate
Danke für die Antwort. Ich habe es gelöst, indem ich das Klickereignis am Ende der Verarbeitung in Kombination mit setInterval aufgerufen habe. Festlegen des Intervalls auf 1000 Millisekunden Ich befolge jetzt das Host-Server-Limit für Anforderungen und es beendet den Ajax-Aufruf nicht mehr, bevor die Antwort zurückgegeben wird.
Claus
2

Warum deaktivierst du die Schaltfläche nicht und aktivierst sie, nachdem du das Ereignis beendet hast?

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

Viel Spaß beim Codieren !!!

Ravia
quelle
0

Einige Ergänzungen zu JoshRivers Antwort gemäß meinem Fall;

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

Es wird nur einen API-Aufruf geben und diese beiden Funktionen verbrauchen das gleiche Ergebnis.

Kadir Can
quelle
0

Sperren werden in JS weiterhin verwendet. Nach meiner Erfahrung musste ich nur Sperren verwenden, um zu verhindern, dass Spam auf Elemente klickt, die AJAX-Aufrufe ausführen. Wenn Sie einen Loader für AJAX-Aufrufe eingerichtet haben, ist dies nicht erforderlich (sowie das Deaktivieren der Schaltfläche nach dem Klicken). Aber so oder so habe ich das zum Sperren verwendet:

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

Verwendung:

Manuelles Entsperren (normalerweise für XHR)

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

Automatische Entsperrung

LockCallback('someKey',() => { 
    //do stuff
})
Bodokh
quelle
0

Hier ist ein einfacher Verriegelungsmechanismus, der über das Schließen implementiert wird

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
quelle