async/awaitやPromiseで簡単に配列のイテレーションできるようにする

作ったモジュールをこの記事で紹介しようと思います。
GitHub: [p-iteration](https://github.com/toniov/p-iteration) (Promise-Iteration)


# 問題

`async/await`は非常に便利で、コードは同期処理の書き方に近くなったが、ストレートに書けない場合が割と出てくる。配列のイテレーションはその一つの問題。

例えば、[Array#map()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map)の利用の際にcallbackの中に`await`を使いたい場合がある。

```js
async function getUsers (userIds) {
  const users = userIds.map(async userId => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  });
  // ... do some stuff
  return users;
}
```

だが、上記のコードは思う通り動かない。なぜなら、`map()`は渡されたcallbackを同期に実行するが、上記のコードに渡しているcallbackの中で非同期処理が行われているからだ。

`await`するには`async function`で囲う必要があるので、`async function`をcallbackとして渡した。`async function`はいつもPromiseオブジェクトを返す。上記の場合だと、`return response.json();`は実行し終わった時点で返ってきたPromiseはresolveされるが、`map()`はPromiseがresolveされるのを待たないので、各イテレーションのPromiseはresolveされた時点で`map()`の実行はもう終わっている。結果的に`users`にPromiseの配列になっています。

下記のコードを実行して頂けると行動はわかりやすいと思う。

```js
const users = [1, 2, 3].map(async (n) =>  {
  await "dummy";
  console.log(n);
  return n * 2;
});
console.log(users);
console.log("foo");
```

順番に `[Promise, Promise, Promise]` `foo` `1` `2` `3` が表示される。

`map()`を例として書いたが、この行動は`map()`だけじゃなく、他の[built-in Iteration Methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Iteration_methods)（`forEach()`、`find()`、`every()`、`reduce()`など）も同じ。工夫せずにcallbackの中に非同期処理を行うことはできない。こういった問題はStack Overflowに[よく出てきている](https://stackoverflow.com/search?q=async+await+forEach+%5Bjavascript%5D)。

# p-iterationで楽に解決

`p-iteration`を使うと、スムーズにcallbackに非同期処理をおこなえる。

## map()の例

上記に書いた例を`p-iteration`で書いてみよう。

```js
const { map } = require('p-iteration');

async function getUsers (userIds) {
  const users = await map(userIds, async userId => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  });
  // ... do some stuff
  return users;
}
```

## forEach()

よく使われる`Array#forEach()`の例も書いてみよう。

```js
const { forEach } = require('p-iteration');

function logUsers (userIds) {
  return forEach(userIds, async userId => {
    const response = await fetch(`/api/users/${userId}`);
    const user = await response.json();
    console.log(user);
  });
}
```

## 配列にPromiseが入っている場合

配列の中にもPromiseを入れることは可能。callbackに渡される前にPromiseはunwrapされる。

```js
const { forEach } = require('p-iteration');
const fetchJSON = require('nonexistent-module');

async function logUsers () {
  const users = [
    fetchJSON('/api/users/125'), // returns a Promise
    { userId: 123, name: 'Jolyne', age: 19 },
    { userId: 156, name: 'Caesar', age: 20 }
  ];
  return forEach(users, (user) => {
    console.log(user);
  });
}
```

## プレーンのPromiseでの利用の場合

`async/await`を利用しなくても、プレーンのPromiseの書き方は可能。

```js
const { map } = require('p-iteration');

map([123, 125, 156], (userId) => fetch(`/api/users/${userId}`))
  .then((result) => {
    // ...
  })
  .catch((error) => {
    // ...
  });
```

## 他のメソッド

`map()`と`forEach()`以外、ES5のbuilt-in Iteration Methodsの真似をして、`find()`、`findIndex()`、`some()`、`every()`、`filter()`、`reduce()`も実装した。

詳細は[ドキュメンテーションに参考](https://toniov.github.io/p-iteration/)。

# おわりに

`p-iteration`の紹介でした。

このモジュールが役に立ったら、スターを頂けると嬉しいです。イシューもしくはプルリクエストも待っています！