Dart Futureクラス概要とコンストラクタ

Dar言語StreamクラスtoSet()メソッドDart

Dart Futureクラスの概要とコンストラクタについてメモ書き。公式サイトはこちら

Dartには非同期処理を扱うasyncライブラリがあり、dart:coreライブラリを通して直接使用できる。asycnライブラリにはFutureクラス、Streamクラス、Timerクラス、Zoneクラス等が用意されている。
本章ではFutureクラスを取り上げる。

Futureクラスを使うと完了を待たずに処理を進める事ができる。所謂非同期処理である。 
具体的にはデータベースのクエリやHTTPレスポンス、ファイル読み込みなど、すぐに結果が得られない場面で使う。
Futureクラスの非同期処理はasync関数で制御する。async関数はFutureクラスを返す。
async関数内で処理完了を待つ場合はawaitを使用する。awaitはFutureクラスを返す処理に対して使用できる。

コーディングで確認。asyncを使ってtestFuture()関数を定義する。Futureはジェネリック型なので、戻り値の型を<>内で指定する。今回は値を返さないのでFuture<void>を指定。
awaitはFutureクラスを返す「Future.delayed(Duration(seconds: 2))」に対して指定する。Future.delayedについてはコンストラクタ章に記載。awaitを付与することで2秒スリープする。

void main() {
  testFuture();
  print("処理3");
}

Future<void> testFuture() async {
  print("処理1");
  await Future.delayed(Duration(seconds: 2));
  print("処理2");
}

実行結果。
testFuture()の完了を待たずに「print(“処理3”)」が実行されている事が分かる。
async関数で制御している「print(“処理2”);」はスリープ後に実行される。

処理1
処理3
処理2

上記例でスリープ対象範囲を指定する場合は以下のようにコーディングする。

void main() {
  testFuture();
  print("処理3");
}

Future<void> testFuture() async {
  print("処理1");
  Future.delayed(Duration(seconds: 2), () {
    print("処理4");
  });
  print("処理2");
}

実行結果。
「処理4」がスリープ対象になる。awaitで処理待ちしていないため、「処理2」が先に実行される。

処理1
処理2
処理3
処理4

次はエラーについて確認。
testFuture()関数で存在しないtest.txtファイルをreadAsString()メソッドで読み込む。readAsString()はFutureを返す。
testFuture()は以下①のtry-catchブロックに入れる。なお、Futureの戻り値は以下コードに出てくるthen()メソッドを使用して取得する。

import 'dart:io';

void main() {
  // ①
  try {
    testFuture().then((value) {
      print(value);
    });
  } catch (error) {
    print("try-catch:${error.toString()}");
  }
}

Future<String> testFuture() async {
  var readTest = await File('test.txt').readAsString();
  return readTest;
}

実行結果。
エラーハンドリングができず(Unhandled exception)、プログラムがクラッシュした。

Unhandled exception:
PathNotFoundException: Cannot open file, path = 'test.txt' (OS Error: No such file or directory, errno = 2)

try-catchブロックだけでは非同期のエラーを補足できない。Futureの完了がthen()メソッドやcatchError()メソッド呼び出しより早い場合、「unhandled error」になってしまう。
シンタックスエラーにもならないため、潜在的なバグになる。

then()メソッド等実行前にエラーを捕捉するため、testFuture()側にtry-catch文を入れエラーをスローする。呼び出し側でcatchError()メソッドを使用し、例外処理を行う。

void main() {
  testFuture().then((value) {
    print(value);
  }).catchError((value) {
    print(value.toString());
  });
}

Future<String> testFuture() async {
  try {
    var readTest = await File('test.txt').readAsString();
    return readTest;
  } catch (e) {
    throw e.toString();
  }
}

実行結果。
エラーを補足。例外処理が機能した。

PathNotFoundException: Cannot open file, path = 'test.txt' (OS Error: No such file or directory, errno = 2)

ちなみに同期処理だと①でエラーを補足できる。同期用のファイル読み込みメソッドreadAsStringSync()を使って確認。

import 'dart:io';

void main() {
  // ①
  try {
    testSync();
  } catch (error) {
    print("try-catch:${error.toString()}");
  }
}

String testSync() {
  var readTest = File('test.txt').readAsStringSync();
  return readTest;
}

同期処理の実行結果。

try-catch:PathNotFoundException: Cannot open file, path = 'test.txt' (OS Error: No such file or directory, errno = 2)

以降でFutureクラスの各コンストラクタを確認する。
コンストラクタ確認のみなので、asyncによる制御とエラーハンドリングは行わない。

Future<T>()

Future<T>()はasyncライブラリのTimer.run()メソッドを使用し、非同期処理を行うコンストラクタ。

コーディングで確認。

void main() {
  Future<int>(() {
    int i = 5 * 6;
    return i;
  }).then((value) {
    print(value);
  });
  print("処理2");
}

実行結果。

処理2
30

Future<T>.delayed()

Future<T>.delayed()は指定した時間スリープ後、処理を実行するコンストラクタ。
第1引数はDurationでスリープ時間を指定し、第2引数で処理を指定する。第2引数はオプション引数で省略できる。

コーディングで確認。

void main() {
  Future<int>.delayed(Duration(seconds: 1), () {
    return 3 * 3;
  }).then((value) {
    print(value);
  });
  print("処理2");
}

実行結果。

処理2
9

Future<T>.error()

Future<T>.error()はFuture型のエラーを返すコンストラクタ。

コーディングで確認。testFuture()関数内でFuture<T>.error()を使う。

void main() {
  testFuture(2).then((value) {
    print(value);
  }).catchError((value) {
    print(value.toString());
  });
  print("処理2");
}

Future<String> testFuture(int a) {
  if (a == 2) {
    return Future<String>.error(Exception('Error Test'));
  }
  return Future(() => "処理1");
}

実行結果。
非同期でエラーを返すため「処理2」が先に出力されている。

処理2
Exception: Error Test

Future<T>.microtask()

Future<T>.microtask()コンストラクタはZoneクラスのscheduleMicrotask()関数を使って処理を実行する。scheduleMicrotask()関数は他の非同期処理より先に実行される。

Future<T>()コンストラクタ(Timer.run()から実行)とFuture<T>.microtask()コンストラクタの実行順序を確認してみる。

void main() {
  print("処理1");
  // Timer.run1
  Future<String>(
    () {
      return "Timer.run1";
    },
  ).then(
    (value) {
      print(value);
    },
  );
// Timer.run2
  Future<String>(
    () {
      return "Timer.run2";
    },
  ).then(
    (value) {
      print(value);
    },
  );
  // Microtask1
  Future<String>.microtask(
    () {
      return "Microtask1";
    },
  ).then(
    (value) => print(value),
  );
  // Microtask2
  Future<String>.microtask(
    () {
      return "Microtask2";
    },
  ).then(
    (value) => print(value),
  );
  print("処理2");
}

実行結果。
Future<T>.microtask()が先に実行された事が分かる。

処理1
処理2
Microtask1
Microtask2
Timer.run1
Timer.run2

Future<T>.value()

Future<T>.value()は引数で渡した値をFuture型で返すコンストラクタ。

以下でFuture<int>を返す関数を定義している。この場合、returnで数値を指定すると戻り値の型がFuture<int>ではないのでシンタックスエラーになる。「Future<int>.value(255);」とする事で戻り値と同じFuture<int>型を簡潔に作成できる

void main() async {
  var test = await getInt(1);
  print(test);
}

Future<int> getInt(int i) {
  if (i == 3) {
    return Future<int>(() => 5 * i);
  } else {
    return Future<int>.value(255);
  }
}

実行結果。

255

コメント

タイトルとURLをコピーしました