[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