2020/3/24 : Dart : Asynchronous programming: futures, async, awaitの訳

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();
}

 

 

 

 

 

参考

https://dart.dev/codelabs/async-await

コメントを残す

メールアドレスが公開されることはありません。