Locks.js 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Locks = void 0;
  4. const defaultStore = typeof window === 'object' && window && typeof window.localStorage === 'object' ? window.localStorage : null;
  5. let _locks;
  6. /**
  7. * Creates a lock manager, which can create exclusive locks across browser tabs.
  8. * Uses `window.localStorage` by default to lock across tabs.
  9. *
  10. * Below example, will wait for 5 seconds to acquire a lock, and then execute
  11. * the function once lock is acquired and release the lock after function
  12. * execution. It will fail with `LOCK_TIMEOUT` error if lock is not acquired
  13. * within the 5 seconds. The lock will acquired for 2 seconds (default 1000ms).
  14. *
  15. * ```ts
  16. * Locks.get().lock('my-lock', 2000, 5000)(async () => {
  17. * console.log('Lock acquired');
  18. * });
  19. * ```
  20. */
  21. class Locks {
  22. constructor(store = defaultStore || {}, now = Date.now, pfx = 'lock-') {
  23. this.store = store;
  24. this.now = now;
  25. this.pfx = pfx;
  26. }
  27. acquire(id, ms = 1000) {
  28. if (ms <= 0)
  29. return;
  30. const key = this.pfx + id;
  31. const lockUntil = this.store[key];
  32. const now = this.now();
  33. const isLocked = lockUntil !== undefined && parseInt(lockUntil, 36) > now;
  34. if (isLocked)
  35. return;
  36. const lockUntilNex = (now + ms).toString(36);
  37. this.store[key] = lockUntilNex;
  38. const unlock = () => {
  39. if (this.store[key] === lockUntilNex)
  40. delete this.store[key];
  41. };
  42. return unlock;
  43. }
  44. isLocked(id) {
  45. const key = this.pfx + id;
  46. const lockUntil = this.store[key];
  47. if (lockUntil === undefined)
  48. return false;
  49. const now = this.now();
  50. const lockUntilNum = parseInt(lockUntil, 36);
  51. return lockUntilNum > now;
  52. }
  53. lock(id, ms, timeoutMs = 2 * 1000, checkMs = 10) {
  54. return async (fn) => {
  55. const timeout = this.now() + timeoutMs;
  56. let unlock;
  57. while (!unlock) {
  58. unlock = this.acquire(id, ms);
  59. if (unlock)
  60. break;
  61. await new Promise((r) => setTimeout(r, checkMs));
  62. if (this.now() > timeout)
  63. throw new Error('LOCK_TIMEOUT');
  64. }
  65. try {
  66. return await fn();
  67. }
  68. finally {
  69. unlock();
  70. }
  71. };
  72. }
  73. }
  74. exports.Locks = Locks;
  75. Locks.get = () => {
  76. if (!_locks)
  77. _locks = new Locks();
  78. return _locks;
  79. };