Skip to content

クラスメソッド

デコレーター

INFO

asyncmux をデコレーターとして使用する場合、ステージ 3 のデコレーターがサポートされている実行環境、またはそれを再現した実装が必要です。

@asyncmux

クラスメソッドデコレーター @asyncmux は、書き込みロックを獲得し、クラスメソッドを排他的に実行します。

シグネチャー

ts
function asyncmux<TMethod extends AsyncClassMethod>(
  method: TMethod,
  context: unknown,
): TMethod;

引数

method

  • : (this: any, ...args: any) => Promise<any>

Promise オブジェクトを返すクラスメソッドです。

context

  • : unknown

ステージ 3 のデコレーターの Context オブジェクトです。

例外

DecoratorSupportError

引数 context がステージ 3 のデコレーターの Context オブジェクトではないと判定された場合に投げられます。

LockEscalationError

読み取りロック中に、この書き込みロックを獲得しようとした場合に投げられます。

使用例

以下の例では、処理 B の方が早く ID をコンソールに出力しそうですが、排他制御を行っているため、実際には処理 A が ID をコンソールに出力してから、処理 B が続きます。

ts
import { asyncmux } from "asyncmux";

class Service {
  @asyncmux
  async update(duration: string, id: string) {
    await sleep(duration);
    console.log(`update: ${id}`);
  }
}

const service = new Service();

const updatePromiseA = service.update("3s", "A");
const updatePromiseB = service.update("1s", "B");

await Promise.all([updatePromiseA, updatePromiseB]);
// update: A
// update: B

以下の例では、書き込みロック中のクラスメソッド内で、書き込みロックを要求する他のクラスメソッドを実行します。

ts
import { asyncmux } from "asyncmux";

class Service {
  @asyncmux
  async create() {
    const updatePromiseA = service.update("3s", "A");
    const updatePromiseB = service.update("1s", "B");

    await Promise.all([updatePromiseA, updatePromiseB]);
  }

  @asyncmux
  async update(duration: string, id: string) {
    await sleep(duration);
    console.log(`update: ${id}`);
  }
}

const service = new Service();

await service.create();
// update: A
// update: B

以下の例では、書き込みロック中のクラスメソッド内で、読み取りロックを要求するクラスメソッドを実行します。

ts
import { asyncmux } from "asyncmux";

class Service {
  @asyncmux
  async create() {
    const readPromiseA = service.read("3s", "A");
    const readPromiseB = service.read("1s", "B");

    await Promise.all([readPromiseA, readPromiseB]);
  }

  @asyncmux.readonly
  async read(duration: string, id: string) {
    await sleep(duration);
    console.log(`read: ${id}`);
  }
}

const service = new Service();

await service.create();
// read: B
// read: A

以下の例では、読み取りロック中のクラスメソッド内で、書き込みロックを要求するクラスメソッドを実行します。

ts
import { asyncmux } from "asyncmux";

class Service {
  @asyncmux
  async create() {
    // ...
  }

  @asyncmux.readonly
  async read() {
    await this.create();
  }
}

const service = new Service();

await service.read(); // LockEscalationError

@asyncmux.readonly

クラスメソッドデコレーター @asyncmux.readonly は、読み取りロックを獲得し、クラスメソッドを排他的に実行します。複数の @asyncmux.readonly デコレーターは並行して実行されます。

シグネチャー

ts
function asyncmux.readonly<TMethod extends AsyncClassMethod>(
  method: TMethod,
  context: unknown,
): TMethod;

引数

method

  • : (this: any, ...args: any) => Promise<any>

Promise オブジェクトを返すクラスメソッドです。

context

  • : unknown

ステージ 3 のデコレーターの Context オブジェクトです。

例外

DecoratorSupportError

引数 context がステージ 3 のデコレーターの Context オブジェクトではないと判定された場合に投げられます。

使用例

以下の例では、処理 A よりも処理 B の方が実行時間が短いため、読み取りロック同士であれば並列に処理され、処理 B が先に ID をコンソールに出力します。

ts
import { asyncmux } from "asyncmux";

class Service {
  @mutex.readonly
  async read(duration: string, id: string) {
    await sleep(duration);
    console.log(`read: ${id}`);
  }
}

const service = new Service();

const readPromiseA = service.read("3s", "A");
const readPromiseB = service.read("1s", "B");

await Promise.all([readPromiseA, readPromiseB]);
// read: B
// read: A

以下の例では、読み取りロック中のクラスメソッド内で、さらに読み取りロックを要求する他のクラスメソッドを実行します。これらはすべて共有ロックとして扱われるため、並列に実行されます。

ts
import { asyncmux } from "asyncmux";

class Service {
  @mutex.readonly
  async list() {
    const readPromiseA = service.read("3s", "A");
    const readPromiseB = service.read("1s", "B");

    await Promise.all([readPromiseA, readPromiseB]);
  }

  @mutex.readonly
  async read(duration: string, id: string) {
    await sleep(duration);
    console.log(`read: ${id}`);
  }
}

const service = new Service();

await service.list();
// read: B
// read: A

関数型 API

asyncmux()

関数 asyncmux は、クラスメソッド内で書き込みロックを獲得します。

シグネチャー

ts
function asyncmux(
  this_: object,
  options?: {
    signal?: AbortSignal;
  },
): Promise<Disposable & {
  unlock(): void;
}>;

引数

this_

  • : object

クラスのインスタンスです。

options.signal

  • : AbortSignal

ロックの獲得を中止するためのシグナルです。

返値

アンロックするためのオブジェクトで解決される Promise オブジェクトです。アンロックするためには、using 構文を使うか、このオブジェクトの .unlock() メソッドを呼び出します。アンロックしたあと、.unlock() メソッドを呼び出すことはできません。

例外

引数 options.signal がすでに中止されている場合、options.signal.reason を投げます。

使用例

以下の例では、クラスメソッド内で using 構文を使用して書き込みロックを獲得します。

ts
import { asyncmux } from "asyncmux";

class Service {
  async create(data: string, signal?: AbortSignal) {
    using _ = asyncmux(this, { signal });
    // ...
  }
}

以下の例では、クラスメソッド内で書き込みロックを獲得します。

ts
import { asyncmux } from "asyncmux";

class Service {
  async create(data: string, signal?: AbortSignal) {
    let mux;
    if (__STRICT_MODE__) {
      mux = asyncmux(this, { signal });
    }

    try {
      // ...
    } finally {
      mux?.unlock();
    }
  }
}

asyncmux.readonly()

関数 asyncmux.readonly は、クラスメソッド内で読み取りロックを獲得します。

シグネチャー

ts
function asyncmux.readonly(
  this_: object,
  options?: {
    signal?: AbortSignal;
  },
): Promise<Disposable & {
  unlock(): void;
}>;

引数

this_

  • : object

クラスのインスタンスです。

options.signal

  • : AbortSignal

ロックの獲得を中止するためのシグナルです。

返値

アンロックするためのオブジェクトで解決される Promise オブジェクトです。アンロックするためには、using 構文を使うか、このオブジェクトの .unlock() メソッドを呼び出します。アンロックしたあと、.unlock() メソッドを呼び出すことはできません。

例外

引数 options.signal がすでに中止されている場合、options.signal.reason を投げます。

使用例

以下の例では、クラスメソッド内で using 構文を使用して読み取りロックを獲得します。

ts
import { asyncmux } from "asyncmux";

class Service {
  async read(data: string, signal?: AbortSignal) {
    using _ = asyncmux.readonly(this, { signal });
    // ...
  }
}

以下の例では、クラスメソッド内で読み取りロックを獲得します。

ts
import { asyncmux } from "asyncmux";

class Service {
  async read(data: string, signal?: AbortSignal) {
    let mux;
    if (__STRICT_MODE__) {
      mux = asyncmux.readonly(this, { signal });
    }

    try {
      // ...
    } finally {
      mux?.unlock();
    }
  }
}