皆さまは 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
以上。