Anakunda / async-mutex

// ==UserScript==
// ==UserLibrary==
// @name         async-mutex
// @namespace    https://openuserjs.org/users/Anakunda
// @description  https://github.com/PelionIoT/async-mutex
// @copyright    2021, PelionIoT (https://github.com/PelionIoT)
// @license      MIT
// @version      1.00
// @author       PelionIoT
// @exclude      *
// ==/UserScript==
// ==/UserLibrary==

'use strict'

/**
 * An asynchronous analog to a mutex
 * in multithreaded programming. This is
 * used to coordinate access to a series
 * of asynchronous callbacks or chained
 * promises. In asynchronous programming
 * the same chain of promises executed
 * twice can become intermixed during
 * execution such that the sequence of
 * the first effectively happens in parallel
 * with the second.
 *
 * @class AsyncMutex
 * @constructor
 * * @example
 * ```
 * let lock = new AsyncMutex()
 *
 * function cs() {
 *     lock.acquire().then(function() {
 *         ...
 *     }).then(function() {
 *         ...
 *     }).then(function(result) {
 *         lock.release()
 *         return result
 *     }, function(error) {
 *         lock.release()
 *         throw error
 *     })
 * }
 * ```
 */
class AsyncMutex {
	constructor() {
		this.queue = [ ];
		this.acquired = false;
	}

	/**
	 * This method returns a promise that resolves
	 * as soon as all other callers of acquire()
	 * have invoked release(). When the promise
	 * resolves, you can access to critical section
	 * or protected resource
	 *
	 * @method acquire
	 * @return {Promise}
	 */
	acquire() {
		let self = this;
		return new Promise(function(resolve, reject) {
			if (self.acquired) self.queue.push(resolve); else {
				self.acquired = true;
				resolve();
			}
		});
	}

	/**
	 * This method indicates that you are done
	 * with the critical section and will give
	 * control to the next caller to acquire()
	 *
	 * @method release
	 */
	release() {
		let next = this.queue.shift();
		if (next) next(); else this.acquired = false;
	}

	/**
	 * This method returns the length of the
	 * number of threads waiting to acquire
	 * this lock
	 */
	queueSize() { return this.queue.length }
}

class AsyncLockMap {
	constructor() { this.map = new Map() }

	acquire(key) {
		if (!this.map.has(key)) this.map.set(key, new AsyncMutex());
		return this.map.get(key).acquire();
	}

	release(key) {
		if (this.map.has(key)) {
			this.map.get(key).release();
			if (this.map.get(key).queueSize() == 0) this.map.delete(key);
		}
	}
}