LoginSignup
31
31

More than 5 years have passed since last update.

Array 操作関数の callback に async/await な関数を指定したい

Posted at

ちょっと必要になったので考えてみた。実際には Array.prototype.mapArray.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
})
31
31
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
31
31