1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Promise.withResolvers みたいな AsyncIterator.withResolvers がほしい

Last updated at Posted at 2025-02-27

皆さまは Promise.withResolvers() は使っておられますでしょうか?

new Promise() と違ってネストを浅くでき、また、 wrap しなくていいので こじんまりと使えるのが利点です。

で、で、でです。

Promise.withResolvers みたいな AsyncIterator.withResolvers がほしい

本題 です。

Promise.withResolvers() だと resolve / reject / promise を生やしていましたが
AsyncIterator.withResolvers() だと resolve / reject / values / complete を生やして 外から操作できるようにするというニュアンスです。

つまりこんな感じのメソッドが欲しいという話です。

(globalThis.AsyncIterator ??= {}).withResolvers ??=
/**
 * @template T
 */
function withResolvers() {
  let completed = false;
  const DONE = {};
  const resolvers = [withResolvers()];
  return { resolve, reject, complete, values: values() };
  /**
   * @param {T} result
   */
  function resolve(result) {
    const resolver = resolvers.at(-1);
    resolver?.resolve(result);
    if (result !== DONE)
      resolvers.push(withResolvers());
  }
  /**
   * @param {never} reason
   */
  function reject(reason) {
    const resolver = resolvers.at(-1);
    if (resolver) {
      resolver.reject(reason);
      complete();
    }
  }
  function complete() {
    resolve(DONE);
  }
  async function* values() {
    do {
      const resolver = resolvers[0];
      if (!resolver.status) await resolver.promise;
      if (resolver.status === "rejected") resolver.rethrow();
      if (resolver.status === "fulfilled") resolvers.shift();
      if (resolver.result === DONE) break;
      yield resolver.result;
    } while (resolvers.length > 0);
  }
  /**
   * @template T
   */
  function withResolvers() {
    const {
      promise,
      resolve: resolve_,
      reject: reject_,
    } = Promise.withResolvers();
    let status = undefined;
    let result = undefined;
    let reason = undefined;
    /**
     * @param {T} r 
     */
    function resolve(r) {
      if (status) return;
      status = "fulfilled";
      result = r;
      resolve_(r);
    }
    /**
     * @param {never} r 
     */
    function reject(r) {
      if (status) return;
      status = "rejected";
      reason = r;
      reject_(r);
    }
    return {
      /** @returns {"fulfilled"|"rejected"|undefined} */
      get status() {
        return status;
      },
      /** @returns {T} */
      get result() {
        return result;
      },
      rethrow() {
        throw reason;
      },
      promise,
      resolve,
      reject,
    };
  }
};

使い方としては次の様な感じです。

const {resolve, values, complete} = AsyncIterator.withResolvers();
element1.addEventListener("click", resolve, {signal});
element2.addEventListener("click", resolve, {signal});
signal.addEventListener("abort", complete);

for await (const event of values) {
  console.log(event);
}

複数 もしくは 単数 の 連続したイベントを ループで処理したい という需要はあるのでは?と思います。(無い?

例えば window を別途開いてそちらの通信を連続して処理するとき

const win = window.open(..., '_blank');
const {resolve, values, complete} = AsyncIterator.withResolvers();
win.addEventListener('message', ({data}) => resolve(data));

// #region window が閉じるのを監視
(async ()=> {
    while (!win.closed) {
        await new Promise((resolve) => setTimeout(resolve, 1000));
    }
    complete();
})();
// #dendregion

// #region entry point
for await (const data of values) {
    // doing... 
}
// #endregion

以上。

参考

playground

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?