32
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-08-02

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

問題

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

例えば、Array#map()の利用の際にcallbackの中にawaitを使いたい場合がある。

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の配列になっています。

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

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 MethodsforEach()find()every()reduce()など)も同じ。工夫せずにcallbackの中に非同期処理を行うことはできない。こういった問題はStack Overflowによく出てきている

p-iterationで楽に解決

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

map()の例

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

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()の例も書いてみよう。

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される。

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の書き方は可能。

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()も実装した。

詳細はドキュメンテーションに参考

おわりに

p-iterationの紹介でした。

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

32
18
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
32
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?