Contents
Asynchronous programming: futures, async, await
何故非同期なコードが重要なのか?(Why asynchronous code matters)
非同期な処理は、他の処理が終わるのを待つ間、あなたのプログラムを完璧に機能させます。以下に一般的な非同期処理を示します。
- ネットワークからデータを取ってくる。
- データベースに書き込む。
- ファイルからデータを読み込む。
Dartで非同期処理を行うために、Futureクラスとasync、awaitキーワードを使用します。
例:非同期関数の正しく無い使用例
下記のサンプルは非同期関数(fetchUserOrder())の間違った使い方の例です。あとであなたはasync、awaitキーワードを使用してこのサンプルを修正します。このサンプルを実行する前に、どこが間違いなのか考えてみましょう。結果はどうなるでしょうか?
sample1-1
// このサンプルはDartの非同期処理の間違ったコードです。 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>' */
以下にsample1-1が、fetchUserOrder()関数が生成した文字列を表示できなかった原因を表示します。
- fetchUserOrder()関数は、遅延の後に、ユーザーのオーダー「Large Latte」を表す文字列を提供する非同期関数です。指定した秒数経過後に’Large Latte’を返すコールバックを指定されたFutureインスタンスを返す。
- ユーザーのオーダーを取得するために、createOrderMessage()関数はfetchUserOrder()関数を呼び出し、fetchUserOrder()関数実行終了を待つ必要があります。しかしcreateOrderMessage()関数はfetchUserOrder()関数の実行終了を待たないので、createOrderMessage()関数は、最終的にfetchUserOrder()関数が提供する文字列値の取得に失敗します。
- 代わりに、createOrderMessage()関数は、処理が終わっていない保留の状態を表す「uncompleted future」を取得します。次のセクションでさらにfutureについて学びます。
- createOrderMessage()関数がユーザーのオーダーを表す文字列を取得できなかったので、sample1-1は”Large Latte”と表示できませんでした。そして代わりに“Your order is: Instance of ‘_Future'”と表示されました。
次のセクションで、sample1-1でコンソールに表示させたい文字列”Large Latte”を表示できるようにするために、futures、async、awaitについて学びます。
futureとは何か?
futureはFutureクラスのインスタンスです。futureは非同期処理の結果を表します。そしてfutureは「uncomplete(未完了)」「complete(完了)」の二つの状態を持ちます。
NOTE
Uncomletedはfutureが値を生成する前の状態を示すDart用語です。
Uncompleted
非同期関数を呼び出すと、uncompleted futureを返します。そのfutureは非同期関数の非同期処理の終了(結果)、あるいはエラーが投げられることを待ちます。
Completed
非同期処理が成功すると、futureは結果の値とともにcomplete状態になります。そうでなければエラーと共にcomplete状態になります。
Completing with a value
Future<T>型のfutureはT型の値と共にcomplete状態になります。例えば、Future<String>型のfutureは、文字列値を生成します。futureが通常の値を生成しない場合そのfutureの型はFuture<void>です。
Completing with an error
もし何らかの原因で非同期関数が失敗した場合、futureはエラーと共にcomplete状態になります。
Example:はじめてのfuture
sample1-2では、fetchUserOrder()関数はfutureを返します。そのfutureはコンソールに’Large Latte’と表示した後にcomplete状態になります。fetchUserOrder()関数は値を返しませんので、fetchUserOrder()関数の返り値の型はFuture<void>型です。
sample1-2
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 */
sample1-2では、fetchUserOrder()関数は8行目のprint()関数よりも前に実行されているにもかかわらず、結果は、(“Large Latte”)の表示が後で、8行目の’Fetching user order…’の方が前(先)です。fetchUserOrder()関数のコールバックが3秒の遅延の後に実行されるからです。
Example:エラーと共にcomplete状態になる。
sample1-3を実行してfutureがエラーと共にcomplete状態になる挙動を確認しましょう。少し後にエラーを処理する方法(エラーハンドリング)も学びます。
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()関数が返したfutureは、ユーザーIDが不正であるメッセージを表示して、エラーと共にcomplete状態になりました。
これまでfutureについて、またfutureがどのようにcomplete状態になるかについて学んできました。しかし非同期関数の結果をどのように使えば良いでしょうか?次のセクションで、async、awaitキーワードを使って結果を取得する方法を学びます。
async,awaitキーワードと共にfutureを使う
async、awaitキーワードを使うことで、非同期関数を宣言的な方法で記述し、結果を使うことができるようになります。async,awaitキーワードを使う時、次に示す二つの基本的なガイドラインを覚えておきましょう。
- async関数を定義する時、関数のボディの前にasyncキーワードを記述する。
- async関数の中でのみawaitキーワードが機能する。
次の例はmain()関数を同期関数から非同期関数へ変換する例です。
最初に、asyncキーワードをmain()関数のボディの前に記述します。
void main() async { ··· }
非同期関数が宣言された返り値の型を持つ場合は、返り値の型をFuture<T>としましょう。Tは関数が返す値の型です。関数が明示的に値を返さない場合は、返り値の型はFuture<void>です。
Future<void> main() async { ··· }
今main()関数をasync関数にしましたので、main()関数のボディ内で、futureがcomplete状態になるのを待つために、awaitキーワードを使用することができます。
print(await createOrderMessage());
下の二つのサンプルsample2-1,sample2-2を見てください。async,awaitキーワードを使うことで、非同期的なコードが同期的コードのような結果を出すことが確認できます。
sample2-1 (synchronous functions)
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> */
sample2-2 (asynchronous functions)
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 */
工事中🏗
async,awaitを用いた場合の実行の流れ(Excution flow with async and await)
async関数は、最初のawaitキーワードまでは同期的に実行します。これは、async関数のボディ内の、awaitキーワードの直前までの、全ての同期的なコードは直ちに実行される、ということを意味します。
Example:async関数内の実行(Excution within async functions)
sample3-1
void printOrderMessage () async { print('Awaiting user order...'); var order = await fetchUserOrder(); print('Your order is: $order'); } Future<String> fetchUserOrder() { // Imagine that this function is more complex and slow. return Future.delayed(Duration(seconds: 4), () => 'Large Latte'); } Future<void>main() async { countSeconds(4); await printOrderMessage(); } // You can ignore this function - it's here to visualize delay time in this example. void countSeconds(s) { for( var i = 1 ; i <= s; i++ ) { Future.delayed(Duration(seconds: i), () => print(i)); } } /* Awaiting user order... 1 //←1秒後 2 //←2秒後 3 //←3秒後 4 //←4秒後 Your order is: Large Latte */
工事中🏗
エラーを処理する(Handling errors)
async関数の中でエラーを処理する場合、try-catch文を使います。
sample4-1
try { var order = await fetchUserOrder(); print('Awaiting user order...'); } catch (err) { print('Caught error: $err'); }
async関数の中でも、try-catch文を書くことができます。
Example:async and await with try-catch
sample4-2
Future<void> printOrderMessage() async { try { var order = await fetchUserOrder(); print('Awaiting user order...'); print(order); } catch (err) { print('Caught error: $err'); } } Future<String> fetchUserOrder() { // Imagine that this function is more complex. var str = Future.delayed( Duration(seconds: 4), () => throw 'Cannot locate user order'); return str; } Future<void> main() async { await printOrderMessage(); }
参考