ちょっと必要になったので考えてみた。実際には Array.prototype.map
と Array.prototype.filter
の async/await 版。
Array.prototype.map
先に答えを。イディオムになるかと思いきや関数として定義できてしまった。
async function asyncMap(array, operation) {
return Promise.all(array.map(async item => await operation(item)))
}
async/await な関数は戻り値が Promise
オブジェクトになるので、 Promise.all
ですべてが resolve
されるまで待ってやることで目的の結果が得られる。
次のように使う。ユーザー情報を得る API に id を複数指定して、ユーザーの名前を一括で拾ってくる、というイメージ。
asyncMap([0, 1, 2, 3, 4, 5], async id => {
/*
* { "name": "<user's name>"}
* */
const response = await fetch(`http://example.com/api/users/${id}`)
return response.json().name
})
Array.prototype.filter
こちらは次のようになる。
async function asyncFilter(array, predicate) {
const evaluateds = await asyncMap(array, async item => {
const shouldExist = await predicate(item)
return {
item,
shouldExist,
}
})
return evaluateds
.filter(evaluated => evaluated.shouldExist)
.map(evaluated => evaluated.item)
}
次の 2 点から、そのまま Array.prototype.filter
を適用すると Promise
オブジェクトが評価され、すべての要素が残る結果となってしまう。
-
Array.prototype.filter
は同期的に処理されてしまう - async/await の戻り値は
Promise
オブジェクト
これを避けるために一度 asyncMap
で残すべきかどうかの真偽値を得て、その後同期的に Array.prototype.filter
で処理してやる。
こちらもこんな感じで使う。どこかのグループに属しているユーザーの id を得るイメージ。
const userIdsInGroups = await asyncFilter([0, 1, 2, 3, 4, 5], async id => {
/*
* { "bolongsTo": ["<group name 1>", "<group name 2>", ...] }
* */
const response = await fetch(`http://example.com/api/users/${id}`)
return 0 < response.json().belongsTo.length
})