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でエラーが発生した場合、colorRequestやmassRequestにはハンドラが割り当てられなくなってしまうこともあります。
Proposed Solution
const {
shape,
color,
mass,
} = await Promise.allKeyed({
shape: getShape(),
color: getColor(),
mass: getMass(),
});
文法は意図的にJoint Iterationに合わせてあります。
Promise.allはIterator.zipと同じ。
Iterator.zip = (Array<Iterator<T>>) => Iterator<Array<T>>
Promise.all = (Array<Promise<T>>) => Promise<Array<T>>
Promise.allKeyedはIterator.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.keysやJoint 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になると思います。