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
コメント