[Flutter] リストの各要素にmapでasync関数を適用する方法
小ネタです。
リストの各要素に .map() で async 関数を適用する方法です。
やりたいこと
分かりやすい例として、Firestoreの DocumentSnapshot をモデルクラスに変換するようなコードを考えてみます。
List<Model> models = docs.map((doc) => _fromDoc(doc)).toList();
こんなふうに _fromDoc というメソッドを用意しておいて、 DocumentSnapshot のリストに対して .map() で各 DocumentSnapshot に _fromDoc を適用して、モデルクラスのリストに変換する、といった書き方はよくあると思います。
このとき、 _fromDoc メソッドが async 関数だった場合を考えてみましょう。
例えば
List<Model> models = docs.map((doc) async => await _fromDoc(doc)).toList();
こんなふうにすれば動きそうな気がしませんか?残念ながらこれでは動きません😓
よく考えると分かりますが、
(doc) async => await _fromDoc(doc)
これ自体が async 関数なので戻り値は Model ではなく Future<Model> となります。
なので、
docs.map((doc) async => await _fromDoc(doc)).toList()
これ全体の型は List<Model> ではなく List<Future<Model>> になります。
つまり、
docs.map((doc) => _fromDoc(doc)).toList()
結局元のこれと同じですね。
やり方
ではどうすればいいかというと、Future.wait() を使います。
Future.wait() は Iterable<Future<T>> を Future<List<T>> に変換してくれるので、これを await すれば無事に非同期解決済みの値のリストが手に入るというわけです。
List<Model> models = await Future.wait(docs.map((doc) => _fromDoc(doc)).toList());
これで、意図どおりにリストの各要素に .map() で async 関数を適用できました👍
参考:Asynchronous iterable mapping in Dart - Stack Overflow
参考:async await - Dart - is it possible to change a future.forEach. to a map? - Stack Overflow
Discussion