Skip to content

Proposal: Support function-less navigator.locks.request() returning a disposable lock object #122

@juner

Description

@juner

Summary

I would like to propose an additional overload of navigator.locks.request() that returns a Lock-like object when called without a callback function. This would allow developers to manually release the lock or use JavaScript’s await using (AsyncDisposable) syntax for automatic cleanup.

Motivation

Currently, the Web Locks API usage looks like this:

await navigator.locks.request("name", async lock => {
  // work
});

While this callback style is convenient in some scenarios, it can be limiting in others, such as:

  • When you want to pass the lock object around different scopes.
  • When using libraries like disposable-lock that rely on explicit lock objects and automatic disposal patterns.
  • When you want to integrate with ECMAScript await using / AsyncDisposable syntax for safer automatic cleanup.

The desired additional usage would be:

// Case 1: explicit release
const lock = await navigator.locks.request("name");
// do work...
await lock.release();

Or:

// Case 2: using / auto-release
{
  await using lock = await navigator.locks.request("name");
  // do work...
}
// lock is automatically released

Proposed API

interface LockManager {
  request(name: string, options?: { ifAvailable?: boolean }): Promise<DisposableLock | null>;
}

Where DisposableLock could be defined as:

interface DisposableLock {
  readonly name: string;
  readonly mode: LockMode;

  release(): Promise<void>;
  [Symbol.asyncDispose](): Promise<void>; // for ECMAScript using support
}

This allows developers to safely use await using even if the lock was not obtained.


Potential Concerns / Considerations

  • Backward compatibility with the existing callback-based API
  • Ensuring locks are always released even if the user forgets release()
  • Aligning the behavior with current lifetime rules in the Web Locks API
  • Interaction with reentrancy and contention handling
  • Handling ifAvailable: true cases consistently with both DisposableLock and null

References

Update:

Based on recent feedback from TC39 regarding AsyncDisposable, disposable-lock has been updated so that navigator.locks.request() now returns:

  • a ReleasableLock object when the lock is successfully acquired, or
  • null when the lock could not be obtained (e.g., ifAvailable: true).

This ensures compatibility with await using and aligns with the language-level guidance that await using x = null works safely.

Example usage:

{
  await using lock = await navigator.locks.request("name", { ifAvailable: true });
  if (lock) {
    // lock was acquired, work can proceed
  } else {
    // lock was not acquired, safe to skip
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions