このcodelabでは、非同期処理をfuturesとasync、awaitキーワードを用いて記述する方法を扱います。
このcodelabでは以下の要素を扱います。
- いつ、どのようにasync、awaitキーワードを使うか
- 実行の順番がasync、awaitキーワードを使うことでどのようになるか
- async関数の中で、非同期関数が発するエラーをtry-catch文を使い扱う方法
Contents
なぜ非同期処理が重要なのか?(Why asynchronous code matters)
非同期処理を使うことで、別の処理の終了を待っているときのプログラムが完璧にできます。非同期処理を使う場面として
- ネットワークからデータを取得する
- データベースに書き込む
- ファイルからデータを読み込む
Dartで非同期処理を実行するために、Futureクラスとasync、awaitキーワードを使うことができます。
Example:非同期関数の間違った使い方の例
以下(sample1-1)に非同期関数( fetchUserOrder() )の間違った使い方を示します。その後でasync,awaitを使ってこのサンプルを修正します。このサンプルを実行する前に、結果がどうなるか考えてみてください。
sample1-1
String createOrderMessage () { var order = fetchUserOrder(); return 'Your order is: $order'; } Future<String> fetchUserOrder() { // Imagine that this function is more complex and slow return Future.delayed(Duration(seconds: 4), () => 'Large Latte'); } void main () { print(createOrderMessage()); } //Your order is: Instance of '_Future<String>'
fetchUserOrder()関数が生成した値を表示できなかった理由は、
- fetchUserOrder()関数は非同期関数で、遅延の後、ユーザーのオーダーを表す文字列「Large Latte」を返します。
- ユーザーのオーダーを取得するために、createOrderMessage()関数はfetchUserOrder()関数を呼び出し、fetchUserOrder()関数の実行が終了するのを待つ必要があります。createOrderMessage()関数はfetchUserOrder()関数の実行終了を待ちませんので、fetchUserOrder()関数が生成する文字列値(‘Large Latte’)を取得できません。
- 代わりに、createOrderMessage()関数は、なすべき仕事をペンディングしている状態の表現、つまり「uncompleted future」を取得します。これについては後で学習します。
- createOrderMessage()関数はユーザーのオーダーを表す文字列を取得できませんでしたので、sample1-1は”Large Latte”と表示できませんでした。その代わりに“Your order is: Instance of ‘_Future'”.と表示されました。
fetchUserOrder()関数が工事中🏗
futureとは?(What is a future)
futureは、Futureクラスのインスタンスです。futureは非同期処理の結果を表現します。futureは二つの状態(uncompletedとcompleted)を持ちます。
NOTE
Uncompletedは、値を生成する前(非同期処理が未完了、ということ)の状態を表すDart用語です。
Uncompleted
非同期関数を呼び出すと、非同期関数はuncompleted futureを返します。そのfutureは、非同期関数の非同期処理(←完了するかあるいはエラーを投げる)が終わるのを待ちます。
Completed
非同期処理が成功したら、そのfutureは値と共に完了します。成功しなければ、そのfutureはエラーと共に完了します。
Completing with a value
Future<T>型のfutureは、T型の値と共に完了(complete)します。例えば、Future<String>型のfutureはString型の値を生成します(非同期処理が成功した場合)。futureが利用可能な値を生成しない場合、そのfutureの型はFuture<void>型です。
Completing with an error
何らかの理由で、非同期関数の非同期処理が失敗した場合、futureはエラーと共に完了(complete)します。
sample1-2で、fetchUserOrder()関数は、コンソールに’Large Latte’と表示した後に完了(complete)するfutureを返します。fetchUserOrder()関数は利用可能な値を返しませんので、返り値の型はFuture<void>型です。
sample1-2 Introducing futures
Future<void> fetchUserOrder() { // Imagine that this function is fetching user info from another service or database return Future.delayed(Duration(seconds: 3), () => print('Large Latte')); } void main() { fetchUserOrder(); print('Fetching user order...'); } //Fetching user order... //Large Latte //'Large Latte'は3秒後に表示される。
sample1-2で、fetchUserOrder()関数は、8行目のprint()関数の呼び出しより前に実行されていますが、fetchUserOrder()関数の出力である’Large Latte’よりも前に8行目の表示(“Fetching user order…”)がなされています。これはfetchUserOrder()関数が”Large Latte”の表示の前に3秒遅らせる処理があるからです。
sample1-3
Future<void> fetchUserOrder() { // Imagine that this function is fetching user info but encounters a bug return Future.delayed(Duration(seconds: 3), () => throw Exception('Logout failed: user ID is invalid')); } void main() { fetchUserOrder(); print('Fetching user order...'); } //Fetching user order... //Uncaught Error: Exception: Logout failed: user ID is invalid //3秒後にエラー表示発生
sample1-3では、fetchUserOrder()関数はユーザーIDが不正であることを示すエラーと共に完了(complete)します。
ここまでfutureと、futureがどのように完了するかを見てきましたが、どのように非同期関数の結果を扱えば良いでしょうか?次のセクションで、async,awaitキーワードを利用した結果の取得方法について学びます。
async,awaitを使ったfutureの扱い方(Working with futures:async and await)
async,awaitキーワードを使うことで宣言的な非同期関数の定義と結果の使用が可能になります。
- async関数を定義するため、asyncを関数のボディの前に記述します。
- async関数の中でのみawaitキーワードを使用できます。
ここで、main()関数を同期関数から非同期関数に変換するサンプルを示します。
まず、asyncキーワードを関数のボディの前に記述します。
void main() async {...}
もし関数の返り値の形注釈がある場合、(関数の返り値の型がT型の場合)返り値の型をFuture<T>にアップデートします。関数が明示的に値を返さない場合、返り値の型をFuture<void>型にします。
Future<void> main() async {...}
async関数を定義しているときに、futureが完了(complete)するのを待つためにawaitキーワードを使います。
print(await createOrderMessage());
sample1-4-1 同期関数
String createOrderMessage() { var order = fetchUserOrder(); return 'Your order is: $order'; } Future<String> fetchUserOrder() => // Imagine that this function is // more complex and slow. Future.delayed( Duration(seconds: 2), () => 'Large Latte', ); void main() { print('Fetching user order...'); print(createOrderMessage()); } //Fetching user order... //Your order is: Instance of '_Future<String>'
sample1-4-2 非同期関数
Future<String> createOrderMessage() async { var order = await fetchUserOrder(); return 'Your order is: $order'; } Future<String> fetchUserOrder() => // Imagine that this function is // more complex and slow. Future.delayed( Duration(seconds: 2), () => 'Large Latte', ); Future<void> main() async { print('Fetching user order...'); print(await createOrderMessage()); } //Fetching user order... //Your order is: Large Latte //2秒後にオーダー内容が表示される。
sample1-4-2では、三つsample1-4-1と違う点があります。
- createOrderMessage()関数の返り値の型がString型からFuture<String>型へ変わっている。
- asyncキーワードがcreateOrderMessage()関数とmain()関数のボディの前に記述されている。
- awaitキーワードが非同期関数であるfetchUserOrder()関数とcreateOrderMessage()関数の呼び出しの前に記述されている。
Key terms:
async: 非同期関数の前にasyncキーワードを記述する。
async function: async関数はasyncキーワードが付けられた関数である。
await: 非同期な式のcompleteな結果を取得するためにawaitキーワードを使用する。
asyncとawaitを使用した時の実行の流れ(Excution flow with async and await)
async関数は初めてawaitキーワードが出現するまでは同期的に実行されます。async関数のボディの中で、はじめのawaitキーワードの前の全ての同期的なコードが即座に実行される、ということです。
参考