3
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?

【JavaScript】Promise.all()をハッシュで受け取れるようになる

3
Posted at
await Promise.all([
  foo(),
  bar(),
  baz(),
]);

// 返り値は[1, 2, 3]

このPromiseの返り値は配列です。
どういうことかって、並列実行なのだから本来順番なんて関係ないはずなのに、返り値を意識した瞬間に順番を意識した記述を強いられるということです。

じゃあハッシュで受け取りたくなるじゃないですか。

動かない
await Promise.all({
  foo: foo(),
  bar: bar(),
  baz: baz(),
});

// 返り値は{foo:1, bar:2, baz:3}

これは動きません。
Promise.allの引数はiterableであり、Objectはiterableでないからです。

そんなわけでオブジェクトを渡してオブジェクトで受け取るAwait dictionary of Promisesというproposalが提出されています。
これ2021年に提出されて以来4年ほど動きがなかったのですが、2025年ごろから再始動し始めて2026年6月ついにStage3になりました。
Stage3は仕様が確定していて、あとはブラウザへの実装を待つだけという状態です。

以下は該当のproposal、Await dictionary of Promisesの紹介です。

Await dictionary of Promises

Motivation

プロパティにそれぞれにawaitすると、並列ではなく直列実行になってしまいます。

const obj = {
  shape: await getShape(),
  color: await getColor(),
  mass: await getMass(),
};

Promise.allは助けになりますが、返り値がハッシュではなく配列になるので、混乱の元になる可能性があります。

const [
  color,
  shape,
  mass,
] = await Promise.all([
  getShape(),
  getColor(),
  getMass(),
]);

既存の構文で再現しようとすると冗長になり、不要な変数割り当ても増えます。

const shapeRequest = getShape();
const colorRequest = getColor();
const massRequest = getMass();

const shape = await shapeRequest;
const color = await colorRequest;
const mass = await massRequest;

またawait shapeRequestでエラーが発生した場合、colorRequestmassRequestにはハンドラが割り当てられなくなってしまうこともあります。

Proposed Solution

const {
  shape,
  color,
  mass,
} = await Promise.allKeyed({
  shape: getShape(),
  color: getColor(),
  mass: getMass(),
});

文法は意図的にJoint Iterationに合わせてあります。

Promise.allIterator.zipと同じ。

Iterator.zip = (Array<Iterator<T>>) => Iterator<Array<T>>
Promise.all  = (Array<Promise<T>>)  => Promise<Array<T>>

Promise.allKeyedIterator.zipKeyedと同じ。

type Dict<V> = { [k: string | symbol]: V };

Iterator.zipKeyed = <D extends Dict<Iterator<any>>>(iterables: D)
  => Iterator<{ [k in keyof D]: Nexted<D[k]> }>

Promise.allKeyed  = <D extends Dict<Promise<any>>>(promises: D)
  => Promise <{ [k in keyof D]: Awaited<D[k]> }>

Additional API

Promise.allSettledにも同様に対応します。

const results = await Promise.allSettledKeyed({
    shape: getShape(),
    color: getColor(),
    mass: getMass(),
});
if (results.shape.status === "fulfilled") {
  console.log(results.shape.value);
} else {
  console.error(results.shape.reason)
}

Existing solutions

既存のライブラリ。

ライブラリ Own Symbols
Bluebird.props ×
combine-promises ×
p-props ×

Q & A

Why not a deep-copy option?

ディープコピーに対応しないのはなぜ?

JSON.stringifyを例外として、組み込みのJavaScript APIは一般的にオブジェクトを深く探査することはありません。

Why only own keys?

どうしてキーだけ対応なのか。

これは組み込み関数Object.keysJoint Iterationなどと同じ挙動です。

What about symbol keys?

シンボルはどう?

列挙可能なシンボルも利用可能です。
これは既存のライブラリとは異なりますが、Joint Iterationと同じ挙動です。

Alternatives considered

検討された代替案。

Promise.ownProperties

const {
  shape,
  color,
  mass,
} = await Promise.ownProperties({
  shape: getShape(),
  color: getColor(),
  mass: getMass(),
});

Promise.fromEntries

const {
  shape,
  color,
  mass,
} = await Promise.fromEntries(Object.entries({
  shape: getShape(),
  color: getColor(),
  mass: getMass(),
}));

Promise.all overload

オーバーロードする。

const {
  shape,
  color,
  mass,
} = await Promise.all({
  shape: getShape(),
  color: getColor(),
  mass: getMass(),
});

新たな名前を導入せずに済みますが、入力形式によって出力形式が変わることや、引数を誤ったときの挙動が変わるなどリスクがあります。

Promise.all(p1, p2, p3);
// ❌ こうするつもりだったが間違えた Promise.all([p1, p2, p3])

現在は例外が発生しますが、オーバーロード後はエラーが出ないうえに意図した動作になりません。

Dedicated syntax

専用構文を導入する。

async const shape = getShape();
async const color = getColor();
async const mass = getMass();

const obj = await {
  shape,
  color,
  mass: Math.max(0, mass),
};

async constの変数は暗黙的にawaitされます。

感想

普段はそんなに気にしないけど、たまに「あれ、こうしたいときどうすればいいんだっけ」ってなるやつだ。
そんなときの書き方が少し便利になりますね。

Firefoxは2026/09/01リリース予定のFirefox155で実装されました。
Safariはプレビュー版のTP250で実装された、って言われてるのをどこかで見たのですがリリースノートには書かれていなかったので真偽不明。
Chromeは情報が見当たりませんでした。
まあそのうちSafariに実装されてStage4になると思います。

3
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
3
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?