AsyncIterator.withResolvers() を for await ..of の break 対応

前の記事の改稿となります。

https://qiita.com/juner/items/a805099707b944274b74

前のだと break すると break 後の complete の呼び出しに問題が発生していたので、その対応です。

```js
AsyncIterator.withResolvers ??= () => {
  let controller;
  let closed = false;
  const abortController = new AbortController();
  const signal = abortController.signal;

  const stream = new ReadableStream({
    start(controller_) {
      controller = controller_;
    },
    cancel(reason) {
      if (!closed) {
        controller.error(reason);
        closed = true;
      }
      abortController.abort(reason);
    },
  });
  const [values, complete] = (() => {
    const values = stream.values();
    const originalReturn = values.return;
    // for of .. break support 
    values.return = complete;
    return [values, complete];
    function complete(value) {
      if (closed) return;
      controller.close();
      closed = true;
      abortController.abort();
      return originalReturn.call(values, value);
    }
  })();

  return {
    values,
    resolve: controller.enqueue.bind(controller),
    complete: () => complete(),
    reject: controller.error.bind(controller),
    signal,
  };
}
```

機能は次の通りです。
1. **非同期イテレータの生成**: `for await...of` 構文で使用可能な非同期イテレータを生成します。
2. **外部からの制御**: `resolve`、`complete`、`reject` プロパティを通じて、イテレータに値を追加したり、完了させたり、エラーを発生させたりすることができます。
3. **イテレータの早期終了のサポート**: `for await...of` ループ内で `break` などを使用してイテレータが早期に終了した場合でも、イテレータが適切にクリーンアップされるようにします。

戻り値のプロパティ

- `values`: 生成された非同期イテレータです。for await...of 構文で使用できます。
- `resolve`: イテレータに値を追加するための関数です。
- `complete`: イテレータを正常に完了させるための関数です。
- `reject`: イテレータにエラーを発生させるための関数です。
- `signal`: イテレータのキャンセルを監視するための AbortSignal です。

って大分複雑になってきた……。 シンプルじゃないですね。

とりあえず使い方的な サンプル置いておきます。

具体的には下記のことを行うサンプルです。

1. open ボタンを押すことでダイアログを開きます
2. ダイアログを開いたら value1 / value2 / value3 及び close ボタンが押せます
3. value1 value2 value3 は値を送り close は閉じます
4. 送られた値のうち value3の場合は 送られた側で ループを break して 処理を終了します。
5. 処理が終了したのであればダイアログも閉じます

```js
openButton.addEventListener("click", async () => {
  const values = openDialog();
  log("open");
  for await (const i of values) {
    log(`${i}`);
    if (i === "value3") break;
  }
  log("close");
});

function openDialog() {
  const controller = new AbortController();
  const {
    values,
    resolve,
    complete,
    signal: innerSignal,
  } = AsyncIterator.withResolvers();
  const signal = AbortSignal.any([controller.signal, innerSignal]);
  dialog.showModal();
  // #region set value
  value1.addEventListener("click", () => resolve("value1"), { signal });
  value2.addEventListener("click", () => resolve("value2"), { signal });
  value3.addEventListener("click", () => resolve("value3"), { signal });
  // #endregion
  // #region close
  closeButton.addEventListener("click", () => controller.abort(), { signal });
  dialog.addEventListener("clise", () => controller.abort(), { signal });
  dialog.addEventListener("cancel", () => controller.abort(), { signal });
  signal.addEventListener("abort", () => {
    dialog.close();
    complete();
  });
  // #endregion
  return values;
}
```

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="mydwWbp" data-pen-title="Untitled" data-user="juner" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/juner/pen/mydwWbp">
  Untitled</a> by juner clarinet (<a href="https://codepen.io/juner">@juner</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>