disposable-lock v2 で チェックボックスをロックするサンプル

に公開

disposable-lock v2 系では、lock(name).request() が次のように改善されました:

  • ロック取得成功 → ReleasableLock オブジェクト
  • ロック取得失敗(ifAvailable: true) → null
  • await using による自動解放対応

この記事では、チェックボックスグリッドでロックを体感できるサンプルを紹介します。

サンプルの概要

  • チェックボックスをクリックすると、最初に空いているロックを取得します
  • ロックが取得できない場合は操作はスキップ
  • チェックを外す際にロックは解除されます
  • 色でロック状況を視覚化

HTML

<div id="grid" class="grid">
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
  <input type="checkbox" />
</div>

CSS

.grid {
  display: grid;
  grid-template-rows: repeat(4, min-content);
  grid-template-columns: repeat(4, min-content);
  gap: 0.5rem;
}

.grid input {
  width: 2rem;
  height: 2rem;
}

.grid input[data-lock] {
  accent-color: hsl(calc(var(--count) * (360 / 16) * 1deg) 80% 40%);
}

/* lock のデータ属性に応じた色付け */
[data-lock="lock:01"] { --count: 0; }
[data-lock="lock:02"] { --count: 1; }
[data-lock="lock:03"] { --count: 2; }
[data-lock="lock:04"] { --count: 3; }
[data-lock="lock:05"] { --count: 4; }
[data-lock="lock:06"] { --count: 5; }
[data-lock="lock:07"] { --count: 6; }
[data-lock="lock:08"] { --count: 7; }
[data-lock="lock:09"] { --count: 8; }
[data-lock="lock:10"] { --count: 9; }
[data-lock="lock:11"] { --count: 10; }
[data-lock="lock:12"] { --count: 11; }
[data-lock="lock:13"] { --count: 12; }
[data-lock="lock:15"] { --count: 13; }
[data-lock="lock:16"] { --count: 14; }

JavaScript

import { lock } from "disposable-lock";
import { withSafeResolvers } from "readable-stream-with-safe-resolvers";

const locks = new WeakMap();
const num = ["01","02","03","04","05","06","07","08","09","10"];

async function getLock() {
  const options = { ifAvailable: true };
  for (const n of num) {
    const { request } = lock(`lock:${n}`);
    const l = await request(options);
    if (l) return l;
  }
  return undefined;
}

const { enqueue, stream } = withSafeResolvers();
const grid = document.getElementById("grid");

grid.addEventListener("change", enqueue);

for await (const event of stream) {
  const target = event.target.closest("input");
  if (!target) continue;
  event.preventDefault();
  // 変更を元に戻して無かったことにする
  target.checked = !target.checked;

  // 既存のロックがある場合は解放
  const oldLock = locks.get(target);
  if (oldLock) {
    target.checked = false;
    locks.delete(target);
    oldLock.release();
    delete target.dataset.lock;
    continue;
  }

  // 新しいロックを取得
  const lockObj = await getLock();
  if (lockObj) {
    target.dataset.lock = lockObj.name;
    locks.set(target, lockObj);
    target.checked = true;
  }
}

ポイント解説

  • ifAvailable: true
    ロックが取得できなければ null を返すので、非同期競合の際も安全に扱える
  • リソースの可視化
    data-lock と CSS による色付けで、どのチェックボックスが占有中かが一目でわかる

以上。

Discussion