如何在 JavaScript 中实现锁

如何在 JavaScript 中实现类似于 C # 中 lock的东西?

所以,为了解释我认为的一个简单的用例是:

用户点击按钮 BB引发 onclick 事件。如果 Bevent-state中,则事件在传播之前等待 Bready-state中。如果 Bready-state中,则 B被锁定并设置为 event-state,然后事件传播。当事件的传播完成时,B被设置为 ready-state

我可以看到类似的事情是如何完成的,只需要从按钮中添加和删除类 ready-state。但是,问题在于用户可以比设置变量更快地在一行中单击两次按钮,所以这种锁定尝试在某些情况下会失败。

有人知道如何在 JavaScript 中实现一个不会失败的锁吗?

131312 次浏览

Lock is a questionable idea in JS which is intended to be threadless and not needing concurrency protection. You're looking to combine calls on deferred execution. The pattern I follow for this is the use of callbacks. Something like this:

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

You can also implement this using DOM event listeners or a pubsub solution.

JavaScript is, with a very few exceptions (XMLHttpRequest onreadystatechange handlers in some versions of Firefox) event-loop concurrent. So you needn't worry about locking in this case.

JavaScript has a concurrency model based on an "event loop". This model is quite different than the model in other languages like C or Java.

...

A JavaScript runtime contains a message queue, which is a list of messages to be processed. To each message is associated a function. When the stack is empty, a message is taken out of the queue and processed. The processing consists of calling the associated function (and thus creating an initial stack frame) The message processing ends when the stack becomes empty again.

...

Each message is processed completely before any other message is processed. This offers some nice properties when reasoning about your program, including the fact that whenever a function runs, it cannot be pre-empted and will run entirely before any other code runs (and can modify data the function manipulates). This differs from C, for instance, where if a function runs in a thread, it can be stopped at any point to run some other code in another thread.

A downside of this model is that if a message takes too long to complete, the web application is unable to process user interactions like click or scroll. The browser mitigates this with the "a script is taking too long to run" dialog. A good practice to follow is to make message processing short and if possible cut down one message into several messages.

For more links on event-loop concurrency, see E

Why don't you disable the button and enable it after you finish the event?

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

Happy coding !!!

Locks are a concept required in a multi-threaded system. Even with worker threads, messages are sent by value between workers so that locking is unnecessary.

I suspect you need to just set a semaphore (flagging system) between your buttons.

Some addition to JoshRiver's answer according to my case;

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

There will be just one api call and these two function will consume same result.

I've had success mutex-promise.

I agree with other answers that you might not need locking in your case. But it's not true that one never needs locking in Javascript. You need mutual exclusivity when accessing external resources that do not handle concurrency.

Locks still have uses in JS. In my experience I only needed to use locks to prevent spam clicking on elements making AJAX calls. If you have a loader set up for AJAX calls then this isn't required (as well as disabling the button after clicking). But either way here is what I used for locking:

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

Usage:

Manual unlock (usually for XHR)

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

Auto unlock

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

Here's a simple lock mechanism, implemented via closure

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

If it helps anyone in 2022+, all major browsers now support Web Locks API although experimental.

To quote the example in MDN:

await do_something_without_lock();


// Request the lock.
await navigator.locks.request('my_resource', async (lock) => {
// The lock has been acquired.
await do_something_with_lock();
await do_something_else_with_lock();
// Now the lock will be released.
});
// The lock has been released.


await do_something_else_without_lock();
  • Lock is automatically released when the callback returns
  • Locks are scoped to origins (https://example.com != https://example.org:8080), and work across tabs/workers.
  • Lock requests are queued (first come-first served); (unlike in some other languages where the lock is passed to some thread at random)
  • navigator.locks.query() can be used to see what has the lock, and who are in the queue to acquire the lock
  • There is a mode="shared" to implement a readers-writers lock if you need it