Search code examples
javascriptasync-awaitlockinges6-promise

Simple Lock That returns false for concurrent calls - javascript


I want to lock an async function from simultaneous execution. The function can be only executed by one caller and it should get true as output after execution where others get false as the return without executing the logical content of the function. Others should not wait for the current caller to complete.

This is what I tried so far:

class Lock {
    constructor() {
        this.locked = false;
    }
    lock(resolve) {
        if (this.locked) {
            resolve(false)
        }
        this.locked = true
        return
    }
    release(resolve) {
        this.locked = false;
        resolve(true)
    }


}

let lock = new Lock();

function myFunction() {
    return new Promise(resolve => {
        lock.lock(resolve);
        //do something - this takes time includes some api calls and db operations
        lock.release(resolve);
    })
}

async function callSite() {
    const executed = await myFunction();
    if(executed){
        //do something
    }
    else{
        //do another thing
    }
}

But seems like it doesn't work as expected. Can anyone help me to improve this?


Solution

  • Promise execution is not always guaranteed to stop immediately after resolving or rejecting; you'll have to return a boolean after you check the lock and exit or continue the promise accordingly.

    Run the below snippet; it's an example having 3 buttons with the same promise and a 3 seconds delay. When one button is running all the others can not execute.

    class Lock {
      constructor() {
        this.locked = false;
      }
    
      lock(resolve) {
        if (this.locked) {
          resolve(false);
          return false;
        }
        this.locked = true
        return true;
      }
    
      release(resolve) {
        this.locked = false;
          resolve(true)
      }
    }
    
    let lock = new Lock();
    
    function myFunction({ resultEl }) {
      resultEl.textContent = "Is running";
        
      return new Promise(resolve => {
        // Check if it's locked
        if (!lock.lock(resolve)) {
          // If it is, return and exit the function even if the promise is already resolved
          return;
        }
    
        // do something - this takes time includes some api calls and db operations
        // just wait 3 seconds for demontration
        setTimeout(() => {
          lock.release(resolve);
        }, 3000);
      })
    }
    
    async function callSite() {
      this.disabled = true;
    
      const executed = await myFunction({ resultEl : this.nextSibling });
        
      this.disabled = false;
    
      if (executed) {
        this.nextSibling.textContent = `Finished at ${(new Date).getTime()}`;
      }
      else {
        this.nextSibling.textContent = `Was NOT executed at ${(new Date).getTime()}`;
      }
    }
    
    document.getElementById('a-button').addEventListener('click', callSite);
    document.getElementById('b-button').addEventListener('click', callSite);
    document.getElementById('c-button').addEventListener('click', callSite);
    div {
      margin-bottom: 10px;
    }
    
    button {
      margin-right: 5px;
    }
    <div>
      <button id="a-button">Run A</button><span></span>
    </div>
    
    <div>
      <button id="b-button">Run B</button><span></span>
    </div>
    
    <div>
      <button id="c-button">Run C</button><span></span>
    </div>