作ったモジュールをこの記事で紹介しようと思います。
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 Methods(forEach()
、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
の紹介でした。
このモジュールが役に立ったら、スターを頂けると嬉しいです。イシューもしくはプルリクエストも待っています!