Featured image of post コードテイマー【AbortController】消えた検索が、書庫の奥で魔力を啜る〜AbortSignalを鎖に、残影の霧を断つ〜

コードテイマー【AbortController】消えた検索が、書庫の奥で魔力を啜る〜AbortSignalを鎖に、残影の霧を断つ〜

夕暮れになると図書館の魔力が漏れる。打ち直して閉じたはずの検索が、裏で書庫を潜り、鍵を握り続けていた。AbortSignalを潜行前・潜行後・待機中の三つの刻にhonorさせ、残影を霧散させるTypeScriptの契約を、同時保持カーソル数の模擬戦で検証する物語。

魔力が漏れる夕暮れ

その魔物使いを呼んだのは、僕自身の名でも、誰かの紹介でもなかった。図書館の本が、彼女を呼んだ。

僕は、この魔法図書館の司書をしている。半年前、利用者のために検索術を組んだ。閲覧机に手をかざして探したい言葉を念じれば、書庫の奥から探索妖精が頁を一枚ずつ手繰り、見つけた端から机へ結果を流す。打ち込むそばから候補がせり上がってくる、あの感触が評判になった。みんな喜んでくれた。僕も、誇らしかった。

ただ、夕暮れになると、図書館が痩せる。

朝は軽い。昼を過ぎると、検索の応答がわずかに遅れる。日が傾くほど、通路の魔力灯——壁に埋めた魔力結晶の灯り——が目に見えて翳り、夜には、念じても結果が返ってこない。図書館全体の稼働魔力が、どこかへ漏れていく。一日かけて、確かに漏れている。なのに、どこから漏れているのか、僕には突き止められなかった。

カイナの名は、封じ書架——危険な術が綴られた古い蔵書を隔離した一角——の、一冊の奥付で見つけた。何年も前、この図書館で暴れた別の魔獣を鎮めた記録。署名入りの契約録が、本に挟まれたまま残っていた。藁にもすがる思いで、僕はその名を頼った。来てくれたこと自体が、まだ信じられない。

カイナは会釈ひとつで閲覧机に向かい、一度検索し、すぐ別の言葉に打ち直した。また打ち直した。それを何度か繰り返し、翳りはじめた魔力灯へ目をやって、低く言った。「これは、一度叩いて出るものじゃない。出るまで、積む」。

閉じたのは、机の上だけ

僕は、自分の組んだ術式(コード)を巻物に写して差し出した。利用者が言葉を打ち直すたび、机の上の結果は新しいものに差し替わる。閉じたように見える。だから僕は、ずっと「ちゃんと閉じている」と思っていた。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
type Page = { items: string[]; last: boolean };

// 書庫への問い合わせ(探索妖精の一潜り)
type Query = (keyword: string, page: number) => Promise<Page>;

// 閲覧机への書き戻し
type OnResult = (items: string[]) => void;

// 書庫への鍵(保持リソース)
interface Cursor {
  release(): void;
}
type AcquireCursor = () => Cursor;

class ArchiveSearch {
  private acquireCursor: AcquireCursor;
  private query: Query;
  private onResult: OnResult;

  constructor(acquireCursor: AcquireCursor, query: Query, onResult: OnResult) {
    this.acquireCursor = acquireCursor;
    this.query = query;
    this.onResult = onResult;
  }

  async search(keyword: string): Promise<void> {
    const cursor = this.acquireCursor();         // 書庫への鍵を握る
    try {
      let page = 0;
      while (true) {
        const result = await this.query(keyword, page); // 一頁潜る
        this.onResult(result.items);                    // 机へ流す
        if (result.last) break;
        page++;
      }
    } finally {
      cursor.release();                          // 探索が尽きて初めて鍵を返す
    }
  }
}

「妖精は、last の頁にたどり着くまで潜り続けます」と僕は説明した。「書庫の鍵を一本握って、一枚ずつ手繰って、最後まで読み切ったら、鍵を返す(finally)。普通の術です。どこにも、おかしいところは……」

カイナは巻物を最後まで目で追うと、口を開く代わりに、指を上げた。閲覧机ではなく、書架の奥の暗がり——もう誰も覗いていないはずの方角——を、まっすぐ指した。そこには、細い光の糸が、まだ机へ向かって流れ込んでいるように、僕には見えた。

「お前が片付けたのは、机の上だけだ」と彼女は言った。「机の下では、まだ誰かが書庫を潜っている」。

意味が掴めなかった。打ち直せば、前の検索は消える。消えたものが、どうして潜り続けるというのか。

残影を積む

カイナは書架の間に、模擬戦の仕掛けを組みはじめた。書庫への問い合わせ(query)、机への書き戻し(onResult)、書庫の鍵(cursor)を、外から握れる作り物に差し替える。妖精が「いつ頁を手繰り終えるか」を、こちらの手で止めたり進めたりできるようにするためだ。鍵については、今いくつ握られたままかを数えられるようにした。以下、握られている鍵の数を live()、書庫の頁を一枚だけ返す手を give()、止まった妖精をほんの一歩——マイクロタスクを一段——だけ進める小道具を tick() と書く。

「一度叩いても、漏れは出ない」とカイナは言った。「だから、積む」。

彼女はまず検索「ドラゴン」を起こした。妖精が頁0を手繰り、結果が机へ。そこで——利用者がそうするように——打ち直した。検索「ドラゴンの巣」。前の検索は閉じたはず。だが古い妖精を止める合図は、どこにもない。古い妖精は、頁1へ、頁2へと、なおも潜っていく。

カイナは鍵を数えた。一本。打ち直す。二本。古い妖精が握った鍵が、書庫に刺さったまま、戻ってこない。さらに打ち直す。三本、四本。検索を切り替えるたび、見捨てられた妖精が一頭ずつ増え、それぞれが鍵を握り、書庫の底で頁を手繰り続ける。僕は、刺さったまま増えていく鍵を、ただ見ていた。これが——夕暮れにかけて、図書館から魔力が抜けていく、その速さだった。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Before: 打ち直しても、見捨てた検索は潜り続ける
const a = search.search("dragon");                 // 検索A
give("dragon", 0, { items: ["a0"], last: false });
await tick();                                       // A は頁1で潜行待ち。鍵1本
assert.equal(live(), 1);

const b = search.search("dragon-nest");             // 打ち直し=検索B(A は見捨て)
give("dragon-nest", 0, { items: ["b0"], last: false });
await tick();
assert.equal(live(), 2);                            // ★A の鍵がまだ刺さったまま=2本に積もる

live() が、二本」とカイナ。「片付けたはずの検索Aが、まだ鍵を握っている。これが、お前の言う"漏れ"だ」。使い終えたものが解放されず、握ったまま積もって、やがて図書館を枯らす——これをメモリリーク(魔力の継続流出)という。鍵だけじゃない。見捨てた妖精は、もう誰も見ていない机へ、見つけた結果を流し続ける。閉じた画面へ、古い答えを書き込む。

帰し方を、決めていなかった

「閉じたのに」と僕は言った。「なぜ妖精は、まだ潜るんですか」。

カイナは書見台の古い図譜を引き寄せ、一頁をひらいて机に伏せた。輪郭の曖昧な、霧でできた妖の絵だった。「ファントム。残影の霧妖だ」と彼女は言った。「実体を消しても、残影は残る。お前は机を片付けた。だが、机の下に潜らせた妖精に、戻れと言っていない」。

——呼んだのに。帰し方を、決めていなかった。僕は、自分の作った機能の形を、初めて裏側から見た気がした。

カイナは、こんどは術の理屈そのものを、低く、淡々と続けた。聞き取れた範囲で書き留めておく。「この妖精——async の関数だ——には、中断の合図が一本も通っていない。whilelast が立つまで回り続ける。打ち直しは新しい妖精を呼ぶだけで、古い妖精には、何も届かない」。

僕は、鍵の刺さった書庫を見た。確かに、古い妖精へ「やめろ」と告げる線は、どこにも引かれていない。

「型(コンパイル時の検め)は、これを守れない」とカイナは続けた。「型は『鍵を二度握る』ような、状態の矛盾なら弾ける。だが『誰も見ていない処理が、裏で生き続ける』のは、時間の問題だ。型には見えない。これは実行時の設計——合図で締めるしかない」。

書庫に渡しただけでは

合図、と僕は繰り返した。聞いたことはある。AbortController。確か、fetch のような外部の問い合わせに signal を渡しておけば、中断できる仕組みだ。AbortController(鎖を断つ符牒)とは、進行中の処理を中断するための取り決めで、受け手が合図を監視して初めて効く。

「なら」と僕は、自分から踏み込んだ。「書庫への問い合わせに、signal を渡せばいいんじゃないですか」。

カイナは止めなかった。僕の言うとおりに、暫定の鎖を組んだ。query にだけ signal を渡す。

1
2
// 暫定:query にだけ signal を渡す(ループ自身は合図を見ない)
const result = await this.query(keyword, page, signal);

模擬戦を回す。潜行(query 待ち)の最中に打ち直すと、確かに、その潜行は弾けた。「ほら、中断できて……」と言いかけて、僕は鍵を数えた。

まだ、刺さっている。

「妖精が、戻ってくるまで」とカイナ。「合図を書庫に渡しただけだ。妖精自身は、潜ったまま、手が塞がっている。手が戻るまで、鍵は握られたままだ」。彼女は、僕の組んだ暫定の一行を指した。「これが『渡したつもりで、見ていない』だ。signal は、受け手が自分の手で確かめて初めて効く。妖精のループは、まだ一度も合図を見ていない」。

——渡すことと、見ることは、違う。打ち直しが頻繁で、書庫が深いほど、この「手が戻るまで鍵を握る」妖精が積み重なる。漏れは細るが、止まらない。そもそも、その「手が戻るまで」がどれだけ長いかを決めるのは、妖精ではなく、潜った先の書庫だ。律儀に合図を聞いて即座に潜行を投げ返す相手なら、手はすぐ戻る。だが、合図を聞かない——あるいは応えるのが遅い——相手に潜れば、鍵はその分だけ握られたままになる。こちらの都合では、縮められない。

「じゃあ」と僕は、半分見えかけた像をたぐった。「潜っている最中でも、鍵を手放させるには——どうすれば」。

三つの刻に、一本の鎖

カイナの施した手当ては、驚くほど慎ましいものだった。妖精を組み直すでも、鎖を何本も増やすでもない。彼女は合図を一本、妖精の通り道の三つの刻に通しただけだった。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class ArchiveSearch {
  private acquireCursor: AcquireCursor;
  private query: Query;
  private onResult: OnResult;

  constructor(acquireCursor: AcquireCursor, query: Query, onResult: OnResult) {
    this.acquireCursor = acquireCursor;
    this.query = query;
    this.onResult = onResult;
  }

  async search(keyword: string, signal: AbortSignal): Promise<void> {
    const cursor = this.acquireCursor();
    // (c) 潜ったまま(await 待機中)に見捨てられても、手の戻りを待たず即送還
    signal.addEventListener("abort", () => cursor.release(), { once: true });
    try {
      let page = 0;
      while (true) {
        signal.throwIfAborted();                               // (a) 潜行前:見捨てられていれば潜らない
        const result = await this.query(keyword, page, signal); // 合図を妖精にも通す
        signal.throwIfAborted();                               // (b) 潜行後:戻った時に中断済みなら書き戻さない
        this.onResult(result.items);
        if (result.last) break;
        page++;
      }
    } finally {
      cursor.release();                                         // 冪等。正常に読み切ればここで送還
    }
  }
}

違いは、ただ一点きり。signal が通っているか否か。鍵を握り、頁を手繰り、机へ流し、最後に鍵を返す——妖精のすることは何も変わっていない。変わったのは、その妖精が、合図を見るようになったことだけだ。

カイナは三つの刻を、順に指でなぞった。signal.throwIfAborted()——中断済みなら即座に例外を投げて処理を脱出させる一行だ——を、潜る前に置く。「見捨てられていれば、次の頁へは潜らない」。同じ一行を、潜って戻った直後にもう一度置く。「戻った刻に既に見捨てられていれば、古い結果を机へ載せない」。中断したはずの処理が後から完了し、消えた画面を古い答えで上書きする事故——これを破壊的な遅延更新という——を、この一行が封じる。

自分の手で signal.aborted を確かめて回ってもいい、とカイナは付け加えた。「だが await を一つ跨ぐたびに、型の検めは、さっき確かめたことを忘れる。戻ってきた妖精に、もう一度『お前は中断済みか』と型へ教え直す手間が要る。投げて降りる throwIfAborted なら、確かめと脱出が一行で済んで、取りこぼしがない」。

「だが、それだけでは、潜っている最中の妖精は止まらない」と彼女は、暫定で僕が数えた鍵のことを言った。「throwIfAborted は、妖精の手が戻った刻にしか効かない。潜ったまま手が塞がっていれば、見るに見られん」。だから三つめ。合図そのものに、「鍵を手放す」所作を結びつける。signal.addEventListener("abort", ...) だ。「これで、妖精が書庫に潜ったまま見捨てられても、手の戻りを待たず、合図が直に鍵を抜く」。使い終えた鍵をその場で書庫から抜くこと、これを送還という。{ once: true } を添えれば、一度使った見張りの符牒も、そのまま消える——見張り自体が残って漏れることもない。

僕は、三つの一行を見比べて、別々の鎖が三本あるように見えた。そう言うと、カイナは小さく首を振った。

「三つに見えるが、鎖は一本だ。『中断の合図を honor する』——それを、潜る前と、戻った後と、潜っている最中の、三つの刻に通しただけだ」。中断は、こちらが命じれば強制的に止まる類のものではない。受け手が合図を見て、自ら降りる。これを協調的キャンセルという、とカイナは言い添えた。「合図を honor する、というのは、受け手の側で、自分の手で合図を確かめることだ。三つの刻のどれを抜いても、その刻だけ、妖精は合図を見ない」。

呼び出す側は、検索のたびに合図の親(AbortController)を一つ握り、打ち直すときに前のものへ abort() を送る。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class SearchSession {
  private current: AbortController | null = null;
  constructor(private search: ArchiveSearch) {}

  run(keyword: string): Promise<void> {
    this.current?.abort();                       // 前の検索を断つ=残影を送還させる
    const controller = new AbortController();    // 使い捨て。毎回新調する
    this.current = controller;
    return this.search
      .search(keyword, controller.signal)
      .catch((err) => { if ((err as { name?: string })?.name !== "AbortError") throw err; });
  }
}

AbortController は使い捨てだ」とカイナ。「一度断った合図は、元に戻らない。打ち直すたび、新しい合図を渡す」。中断で投げられる例外は、err.name === "AbortError" で見分ける。検索の打ち直しは、書庫の不調ではなく、こちらの都合での幕引きだから、ここで静かに飲み込んでおく。

鍵が、即座に戻る

カイナは、先ほどそっくりそのままの手順を、こんどは合図の通った妖精へ仕掛けた。検索Aを起こし、頁0を流させ、打ち直しの代わりにAの合図へ abort() を送る。

1
2
3
4
5
6
7
8
9
// After: 打ち直し(abort)で、見捨てた検索の鍵は即座に戻る
const ctrlA = new AbortController();
const a = search.search("dragon", ctrlA.signal);
give("dragon", 0, { items: ["a0"], last: false });  // A は頁0を正当に机へ流してから…
await tick();
assert.equal(live(), 1);

ctrlA.abort();                                       // ★打ち直し=前を断つ
assert.equal(live(), 0);                             // A の鍵は即0(潜行待ちでも、手の戻りを待たない)

今度は、鍵が即座に戻った。live() が、abort() の直後に0へ落ちる。abort() は、合図を見張る所作(リスナ)を、その場で——await を一つも挟まない、同じ手番のうちに——呼ぶからだ。だから妖精がまだ書庫に潜ったまま、query の答えを待っている最中でも、送還の所作が、手の戻りを待たずに鍵を抜く。このとき、リスナと、throwIfAborted が投げた例外でたどり着く finally の、両方が鍵を抜きにかかるが、送還は冪等にしてあるので、二度抜いても数を二重には減らさない。暫定で僕が数えた「刺さったままの鍵」が、ここでは一本も残らない。

僕は、もう一つ気になっていたことを訊いた。潜って戻ってきた、ちょうどその瞬間に、もう見捨てられていたら。古い結果は、机に載ってしまわないのか。

カイナは、頁を戻してから合図を送る模擬戦を足した。

1
2
3
4
5
6
7
8
// 潜行から戻った時すでに中断済みなら、古い結果は机に載らない
const p = search.search("dragon", ctrl.signal);
await tick();
give("dragon", 0, { items: ["stale"], last: false }); // 結果は戻ってきたが…
ctrl.abort();                                          // 継続が走る前に中断
await p.catch(() => {});

assert.deepEqual(written, []);                         // ★戻った時に中断済み→机に載せない

机は、空のままだった。頁は確かに戻ってきた。だが妖精は、机へ載せる前に、もう一度合図を見た(await の直後の throwIfAborted)。見捨てられていると分かったから、古い結果を置かずに、そのまま降りた。消えた机に、古い答えが書き込まれることはない。頁を返す(give)のは、妖精の続きを"あとで動かす予定"として列(マイクロタスク)に並べるだけだ。だから、その直後に同じ手番で送る abort() が、続きより先に確定する。仮に順序が逆で、続きが先に走り切って机へ載ったとしても、それは中断より前の、正当な書き込みだ。どちらが先でも、消えた机に古い答えが残ることはない。

そして、中断がなければ——普通に最後まで検索すれば——妖精は今までどおり、全部の頁を机へ流し、鍵を返して、何事もなく終わる。合図を通したことで、正常なときの振る舞いは、一切変わっていない。

Sequence diagram showing a memory leak in a search system where abandoned query fairies fail to release the archive cursor (key)

僕が描いた術の流れは、書庫の底の惨状をまざまざと示していた。机の上を綺麗に片付けたつもりでも、書庫の暗がりでは見捨てられた妖精たちが鍵を握りしめたまま、呪文のように潜り続けている。

「これでは、打ち直すたびに図書館の魔力が痩せるわけです……」 「そうだ。合図がなければ、古い残影を断つ術がない」カイナは、もう一枚の図式を取り出して重ねた。「だが、合図が通った世界では、妖精の去り際はこう変わる」。

Sequence diagram showing successful cancellation using an AbortSignal, where the abandoned search fairy immediately releases the key and terminates

「打ち直した瞬間に、鍵が手放されている……!」 「ああ。abort() の合図が届いた瞬間、潜行の最中であろうとも即座に鍵が抜かれる。古い妖精は未練を残さず消え去り、書庫に残る鍵は常に最新の『一本』だけになる」

図の通り、合図のない世界では、見捨てた検索Aが裏で潜り続け、鍵を握ったまま、Bと並んで魔力を啜る。合図を三つの刻に通すと、Aは打ち直しの瞬間に降り、鍵を手放す。残るのは、最新のBの一本だけになる。

合図を見ない者には、届かない

「これで」と僕は、最後に確かめたかった。「魔力の漏れは、止まりますか。完全に」。

カイナは、答えの代わりに、合図の鎖を一本、机に置いた。そうしてから、ようやく口を開いた。

「この契約が止めるのは『見捨てた処理が、裏で生き続ける』漏れだ。妖精が合図を honor している限り、打ち直せば、即座に降りる。それは確かにやった」。彼女は指を立てた。

「だが、合図を見ない者は、この鎖では縛れない。お前の妖精ではなく、書庫の奥の、合図を honor しない古い術式に潜らせれば、それは戻らない。中断は強制じゃない。受け手が合図を見て、自分から降りる取り決めだ。見ない相手には、届かない」。

「それから」と彼女は、もう一つ釘を刺した。中断と時限を一つの符牒で束ねる便利な道具(AbortSignal.any())もあるが、書庫の精霊(Node)の側に、それがかえって魔力を漏らす不具合や、時限が効かない癖が残っている。「期限だけが要るなら、時限の符牒(AbortSignal.timeout())を、束ねず直に使え。束ねる道具は、今はまだ目を離すな」。

そして、最後に。「書庫が嵐——一時の不調で答えを返せない刻、妖精をすぐまた潜らせれば、書庫はますます応えなくなる。失敗したときに『どれだけ待って、もう一度放つか』。それは、中断とは別の獣だ」。彼女は翳りかけた魔力灯を見上げた。「焦って羽ばたく、雷鳥の話になる」。

honor される処理と、honor されぬ相手。その二つが、はっきりと分かれて見えた。僕が今日授かったのは、万能の符牒じゃない。だが、合図さえ隅々まで通る限り、夕暮れの図書館は、もう痩せない。

通し稽古

カイナは、暴れる検索と手懐けた検索の、両方の模擬戦を、まとめて走らせた。漏れる方には「鍵が積もる」ことを、手懐けた方には「即座に戻る」ことを、それぞれ記録として残してある。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ node --test
▶ After: ArchiveSearch(合図を honor する書庫探索)
  ✔ 打ち直すと見捨てた検索の鍵は即座に送還される
  ✔ 潜っている最中に見捨てても、鍵は手の戻りを待たず即送還される
  ✔ 潜行から戻った時すでに中断済みなら、古い結果は机に載らない
  ✔ 中断がなければ全頁を机へ流し、鍵を送還して正常に終わる
▶ Before: ArchiveSearch(暴走する書庫探索)
  ✔ 打ち直すたび、見捨てた検索の鍵が書庫に刺さったまま積もる
  ✔ 中断がなければ全頁を机へ流し、鍵を送還して正常に終わる
ℹ tests 6
ℹ pass 6
ℹ fail 0

通し稽古、合格。漏れの再現も、その封じも、これからはこの模擬戦が見張り続ける。次に誰かが検索術へ手を入れても、合図を honor し損ねれば、鍵が積もって、檻の中で捕まる。

僕が魔力灯を見上げると、夕暮れなのに、翳っていない。澱んでいた検索が、また打ち込むそばから流れていく。図書館が、息を吹き返していた。

「嵐が来て書庫が黙り込んだとき、焦って妖精を放ち続けたら、もっと応えなくなる」と、僕は声に出して確かめた。作ることだけでなく、畳むところまで考えながら言うのは、初めてだった。「次はきっと、その待ち方の話ですね」。

カイナは、答えを引き取らなかった。代わりに、今宵の契約を書きつけた一枚を、僕の手ではなく、封じ書架の古い一冊へ——彼女自身の署名が眠る、あの本へ——静かに挟み直した。「ここへ戻しておけ。次に困った司書が、お前のように見つける」。

霧の晴れた書架の間に、灯りが戻っていた。


📜 カイナの魔獣契約録(Tamer’s Registry)

  • 魔獣名(クラス/パターン名): ファントム(残影の霧妖)/ メモリリーク・破壊的な遅延更新(AbortController で馴致)
  • 危険度(難易度/バグの影響度): ★★★★☆(一回では露見しない。打ち直し・画面遷移が積もる現場で、じわじわ魔力=メモリと接続を握りつぶす)
  • 主な生態(アンチパターンの特徴):
    • 探索ループに中断の合図(AbortSignal)が一本も通っておらず、whilelast まで回り続ける
    • 打ち直し(再検索)で見捨てられても、古い妖精は鍵(カーソル)を握ったまま書庫を潜り続け、消えた机へ古い結果を書き戻す
    • query にだけ signal を渡しても、ループ自身が throwIfAborted で見ていなければ「渡したつもりで honor していない」=手が戻るまで鍵が残る
  • 契約のポイント(設計の要点):
    • 合図を三つの刻に honor させる単一契約。(a) 潜行前 throwIfAborted=次の潜行を出さない、(b) await 直後 throwIfAborted=古い結果を机へ載せない(遅延更新の封じ)、(c) addEventListener("abort", release, {once:true})=待機中でも即送還
    • 変えたのは「合図が通っているか」1点のみ。鍵取得→ループ→finally 送還のロジックは Before と同一。送還は冪等で、abort 時の即送還と finally 送還が重なっても安全
    • 同時保持カーソル数を模擬戦の物差しに据え、測りにくいリークを決定的に可視化した
  • 契約外事項(保証しないこと):
    • 協調的キャンセルゆえ、合図を honor しない処理(受け手が見ない第三者の術式)は止められない
    • AbortController は使い捨て(abort 後は再利用不可、打ち直しは新調)
    • 一時障害からの賢い再送(待って放つ Retry/Backoff)は別の獣=サンダーバードの領分
    • 中断と時限を束ねる AbortSignal.any() は Node 側に既知の不具合があり、時限のみなら AbortSignal.timeout() を直に使う
  • 現在のステータス: 🟢 残影を送還(合図が隅々まで通る限り)/合図を見ぬ術式は別領分・嵐への再送は後日
comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。