🚀

【Dart】Futureクラスとasync/awaitの基本的な使い方

2020/12/30に公開

簡単なFutureクラスの利用方法と、非同期処理について実行結果とともに確認します。

【Dart】非同期処理の基礎

はじめに

事前知識

  • 時間が掛かる処理を行う場合に、次の処理(画面出力等)を行いつつ、
    その処理を待つ必要がある=非同期処理
  • 非同期処理方法は以下が存在
    • Futureクラスのthen関数を利用する方法
    • asyncとawaitキーワードを利用する方法
  • 非同期処理方法は、「asyncとawait」が推奨されている
    • ネストが少なく、通常の同期処理と同様に記載可能であり、可読性が高いため

Futureについて

  • 通常の同期処理
    • 時間が掛かる処理を行う場合に次の処理(画面出力等)が止まってしまう
      • ※以下はpickAllDocumentsは時間が掛かる処理とする
      • print('Next...');」は、前処理が完了した後に実行されるため、出力されるまでに時間が掛かる
String pickAllDocuments() {
  String docs;
  // 時間が掛かる処理と仮定
  for (int i = 0; i < 100000; ++i) {
    if (i == 99999) docs = 'Finish pickAllDocuments!';
  }
  return docs;
}

void main() {
  final docs = pickAllDocuments();
  print(docs);
  print('Next...');
}

実行結果

Finish pickAllDocuments!
Next...
  • 時間が掛かる関数の返り値の型をFutureとする
    • 変更箇所:2行
      • String pickAllDocuments() {Future<String> pickAllDocuments() {
      • return docs;return Future<String>.value(docs);
    • Futureのインスタンスが即座に変数に格納され、次の処理に進む
Future<String> pickAllDocuments() {
  String docs;
  // 時間が掛かる処理と仮定
  for (int i = 0; i < 100000; ++i) {
    if (i == 99999) docs = 'Finish pickAllDocuments!';
  }
  return Future<String>.value(docs);
}

void main() {
  final docs = pickAllDocuments();
  print(docs);
  print('Next...');
}

実行結果

Instance of '_Future<String>'
Next...

非同期処理:Futureクラスのthen関数を利用

  • Futureクラスのthen関数を利用し、時間が掛かる関数の値処理が終わる前に次の処理実施
    • then関数の引数として与えたコールバック関数は、時間が掛かる関数の値処理が終わった際に実行
Future<String> pickAllDocuments() {
  String docs;
  // 時間が掛かる処理と仮定
  for (int i = 0; i < 100000; ++i) {
    if (i == 99999) docs = 'Finish pickAllDocuments!';
  }
  return Future<String>.value(docs);
}

void main() {
  final docs = pickAllDocuments();
  docs.then((value) => print(value));
  print('Next...');
}

実行結果

Next...
Finish pickAllDocuments!
  • Futureクラスのthen関数を利用し、時間が掛かる関数の値処理が終わった際に次の処理実施
    • then関数の中に複数行追加すると、上から順次処理
Future<String> pickAllDocuments() {
  String docs;
  // 時間が掛かる処理と仮定
  for (int i = 0; i < 100000; ++i) {
    if (i == 99999) docs = 'Finish pickAllDocuments!';
  }
  return Future<String>.value(docs);
}

void main() {
  final docs = pickAllDocuments();
  docs.then((value) {
    print(value);
    print('Next...');
  });
}

実行結果

Finish pickAllDocuments!
Next...

非同期処理:asyncとawaitキーワードを利用

  • asyncとawaitキーワードを利用し、時間が掛かる関数の値処理が終わった際に次の処理実施
    • 方法
      • import 'dart:async';を追加
      • main関数にasyncキーワードを追加
      • pickAllDocuments関数にawaitキーワードを追加
    • 注意
      • awaitの後ろのコードは、Futureの処理(時間が掛かる関数の値処理)が完了後に実施
Future<String> pickAllDocuments() {
  String docs;
  // 時間が掛かる処理と仮定
  for (int i = 0; i < 100000; ++i) {
    if (i == 99999) docs = 'Finish pickAllDocuments!';
  }
  return Future<String>.value(docs);
}

void main() async {
  final docs = await pickAllDocuments();
  print(docs);
  print('Next...');
}

実行結果

Finish pickAllDocuments!
Next...
  • asyncとawaitキーワードを利用し、時間が掛かる関数の値処理が終わる前に次の処理実施
    • 処理の前後が関連するひとまとまりの関数にasyncを追加(例:以下test関数に分離)
Future<String> pickAllDocuments() {
  String docs;
  // 時間が掛かる処理と仮定
  for (int i = 0; i < 100000; ++i) {
    if (i == 99999) docs = 'Finish pickAllDocuments!';
  }
  return Future<String>.value(docs);
}

void test() async {
  final docs = await pickAllDocuments();
  print(docs);
}

void main() {
  test();
  print('Next...');
}

実行結果

Next...
Finish pickAllDocuments!

エラーハンドリング

  • Futureクラスのthen関数を利用の際
    • try-catchでエラーをキャッチ
Future<String> pickAllDocuments() {
  String docs;
  // 時間が掛かる処理と仮定
  for (int i = 0; i < 100000; ++i) {
    throw Exception();
    if (i == 99999) docs = 'Finish pickAllDocuments!';
  }
  return Future<String>.value(docs);
}

void main() {
  try {
    final docs = pickAllDocuments();
    docs.then((value) {
      print(value);
      print('Next...');
    });
  } on Exception catch (e) {
    print('TEST:' + e.toString());
  }
}

実行結果

TEST:Exception
  • Futureクラスのthen関数を利用の際
    • Futureクラスのcatch関数を利用し、エラーをキャッチ
      then関数の中でのエラーをキャッチ(pickAllDocuments関数でのエラーはキャッチ不可)
Future<String> pickAllDocuments() {
  String docs;
  // 時間が掛かる処理と仮定
  for (int i = 0; i < 100000; ++i) {
    if (i == 99999) docs = 'Finish pickAllDocuments!';
  }
  return Future<String>.value(docs);
}

void main() {
  final docs = pickAllDocuments();
  docs.then((value) {
    throw Exception();
    print(value);
    print('Next...');
  }).catchError((e) => print('TEST:' + e.toString()));
}

実行結果

TEST:Exception
  • asyncとawaitキーワードを利用
    • try-catchでエラーをキャッチ
Future<String> pickAllDocuments() {
  String docs;
  // 時間が掛かる処理と仮定
  for (int i = 0; i < 100000; ++i) {
    throw Exception();
    if (i == 99999) docs = 'Finish pickAllDocuments!';
  }
  return Future<String>.value(docs);
}

void main() async {
  try {
    final docs = await pickAllDocuments();
    print(docs);
  } on Exception catch (e) {
    print('TEST:' + e.toString());
  }
  print('Next...');
}

実行結果

TEST:Exception
Next...

【備考】Futureのコンストラクタ

Future.value

  • Futureオブジェクトをすぐに返す
void main() {
  final future = Future<String>.value('TEST');
  future.then((value) => print(value));
  print(future);
}

実行結果

Instance of '_Future<String>'
TEST
  • 基本的には、時間が掛かる格納処理を行うような場合に利用
void main() {
  String str;
  // 時間が掛かる処理と仮定
  for (int i = 0; i < 100000; ++i) {
    if (i == 99999) str = 'TEST';
  }
  final future = Future<String>.value(str);
  future.then((value) => print(value));
  print(future);
}

実行結果

Instance of '_Future<String>'
TEST
  • asyncを利用し、Futureの値をwrapする方法をどちらかというと推奨(文字量が少ないため)
void main() {
  Future<int> test1() => Future<int>.value(1);
  Future<int> test2() async => 10;
  test1().then((value) => print(value));
  test2().then((value) => print(value));
}

実行結果

1
10

Future.delayed

  • 指定した遅延の後に処理を実施
    • 3秒後に「TEST」と出力
void main() {
  final future =
      Future<String>.delayed(const Duration(seconds: 3), () => 'TEST');
  future.then((value) => print(value));
  print(future);
}

実行結果

Instance of '_Future<String>'
TEST
  • 簡単に3秒待って、次の処理を実施
    • 3秒後に「Next...」と出力
void main() async {
  await Future.delayed(Duration(seconds: 3));
  print('Next...');
}

実行結果

Next...

Future.error

  • Futureオブジェクトをすぐに返すが、その後エラー終了
void main() {
  final future = Future<String>.error('Test:ERROR');
  future.then((value) => print(value));
  print(future);
}

実行結果

Instance of 'Future<String>'
Unhandled Exception: Test:ERROR
  • オプションでスタックトレース追加可能
void main() {
  final future = Future<String>.error(
      'Test:ERROR', StackTrace.fromString('Test:Stack Trace'));
  future.then((value) => print(value));
  print(future);
}

実行結果

Instance of 'Future<String>'
Unhandled Exception: Test:ERROR
Test:Stack Trace

Future.sync

  • 一見、valueと変わらなく見える
void main() {
  final future = Future<String>.sync(() => 'TEST');
  future.then((value) => print(value));
  print(future);
}

実行結果

Instance of '_Future<String>'
TEST
  • 特徴としては、syncに与えたコールバック関数が即座に実施
void main() {
  final future = Future<String>.sync(() {
    print('TEST');
    return 'TESTTEST';
  });
  future.then((value) => print(value));
  print(future);
}

実行結果

TEST
Instance of '_Future<String>'
TESTTEST

Discussion