2
\$\begingroup\$

I made an AsyncLock implementation due to a comment on Hacker News saying JavaScript was missing such a necessary idea. I honestly am not terribly familiar with Threaded programming and the concept of Locks (other than what I've taken in my OS and Concurrency class on USU campus.) I know JavaScript is not threaded but the idea of needing only one thing touching a datastructure still applies. I was wondering if any one could give me some tips to improve this implementation if there are any gaping holes.

I decided to use ESNext features that currently only work in a browser that supports async iterators like Chrome 63 (Chrome Canary). Basically you lock by creating a for await of loop and it will generate one value so you can do some changes then it will unlock the lock once the loop section of the code is finished. Using async generators greatly changed the design and allowed me to automatically unlock a lock if the locked section of code throws an error or breaks out of the loop.

Tell me what you think or what can be improved.

There are 3 files below concatenated if you prefer to look at them individually you can look at the bl.ock or gist link below. The 3 files are: gtor.js, AsyncLock.js, and test.js.

/**
gtor.js
this is an esnext version of part of kriskowal's gtor
*/
function deferred() {
  const def = {}
  def.promise = new Promise((resolve, reject) => {
    def.resolve = resolve
    def.reject = reject
  })
  return def
}

class PromiseQueue {
  constructor (values) {
    this.ends = deferred();
    if (values) {
      values.forEach(this.put, this);
    }
  }
  
  put(value) {
    const next = deferred();
    this.ends.resolve({
      head: value,
      tail: next.promise
    });
    this.ends.resolve = next.resolve;
  }
  
  get () {
    var result = this.ends.promise.then(node=>node.head);
    this.ends.promise = this.ends.promise.then(node=>node.tail)
    return result;
  };
}

/**
AsyncLock.js
Contains the main lock mechanism
*/

// private variable work-around
var lengths = new WeakMap();
var setLength = key => lengths.set(key,0);
var incLength = key => lengths.set(key,lengths.get(key)+1);
var decLength = key => lengths.set(key,lengths.get(key)-1);
class PromiseQueueLength extends PromiseQueue {
  constructor () {
    super();
    setLength(this);
  }

  put () {
    super.put();
    incLength(this);
  }

  get () {
    return super.get().then(()=>decLength(this));
  }

  
}

// private variable work-around
var locks = new WeakMap();
var getLock = (key)=>locks.get(key);
var setLock = (key, val)=>locks.set(key, val);
var lock = (key)=>getLock(key).get();
var unlock = (key)=>{
  var lock = getLock(key);
  if(length == 0) {
    lock.put(true);
  }
}
/**
 * Provides a Async Single-threaded lock mechanism.
 */
class AsyncLock {
  
  constructor () {
    setLock(this, new PromiseQueueLength());
    getLock(this).put(true);
  }

  async * [Symbol.asyncIterator] () {
    try {
      yield lock(this);
    } finally {
      unlock(this);
    }
  }
}

/**
test.js
*/

var sleep = (ms)=>new Promise(res=>setTimeout(res, ms));

var lock1 = new AsyncLock();

(async () => {
  for await(var _ of lock1) {
    console.log("here 1a");
    await sleep(1000);
    console.log("here 1b")
  }
})();

(async () => {
  for await(var _ of lock1) {
    console.log("here 2a");
    await sleep(1000);
    console.log("here 2b")
  }
})();

/*
When Lock is working should print:
here 1a
here 1b
here 2a
here 2b
*/

If you prefer to look at it as a gist or bl.ock here are the links.

\$\endgroup\$
  • \$\begingroup\$ Maybe I should make the value of the iteration the resource itself so the value could be guaranteed to not be modified if all they have is the lock. \$\endgroup\$ – John Oct 15 '17 at 2:38
  • \$\begingroup\$ Also note that these would normally be in individual modules so the WeakMaps would make the data private. \$\endgroup\$ – John Oct 15 '17 at 2:41
  • 3
    \$\begingroup\$ You are fighting the language using classes, to many functions, to much bloat, and needing to hack private members, JS has closure (thus private scope) eg this passes your test code function AsyncLock() { const p = () => new Promise(next => nextIter = next ); var nextIter, next = p(); const nextP = () => { const result = next; next = result.then(() => p() ); return result;} nextIter(); return Object.assign({}, { async * [Symbol.asyncIterator] () {try { yield nextP() } } finally {nextIter() } }}});} though ugly due to comment size requierments \$\endgroup\$ – Blindman67 Oct 15 '17 at 10:27
  • \$\begingroup\$ @Blindman67 I know it's been a long time since this was written, but I happened to use your code recently for the AsyncLock when writing a tiny testing framework. So, if you put your comment in an answer I will gladly still accept it. \$\endgroup\$ – John Aug 23 at 4:35
  • \$\begingroup\$ Also most the fighting of the language will go away with private class fields which is stage 3 in the TC39 proposal process github.com/tc39/proposal-private-methods. \$\endgroup\$ – John Sep 4 at 11:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.