この記事はバージョン1.6時点の物です
Dartにおける非同期プログラミング
Dartはシングルスレッド
DartはWebアプリ開発のために設計された言語なのでシングルスレッドで動作します。Dartの非同期プログラミングはシングルスレッドでのコールバックとハンドリングによって実現されます。
コールバック地獄
一般にコールバックは次のような入れ子地獄を容易に引き起こします。
asyncA((a) {
asyncAToB(a, (b) {
asyncBToC(b, (c) {
asyncSetC(c, () {
print("done");
});
});
});
});
これはDartのように関数がオブジェクトである言語であればハンドラーに名前をつけることによって幾分か可読性は上がります。
handleSetC() => print("done");
handleBToC(c) => asyncDone(c, handleSetC);
handleAToB(b) => asyncBToC(b, handleBToC);
handleA(a) => asyncAToB(a, handleAToB);
asyncA(handleA);
しかし入れ子が無くなっても処理の流れは読みにくく、簡単とはいえません。
Futureによる非同期プログラミング
dart:async
パッケージが提供するFuture
は上記のような非同期プログラミングを簡潔に記述できるようにしてくれます。例えば上のコールバックはasyncA
がFuture
を返すようにすると次のように書き換えられます。
asyncA() // returns a Future
.then(asyncAToB)
.then(asyncBToC)
.then(asyncSetC)
.then((_) => print("done");
Future
はEcmaScriptでいうPromiseであり、コールバック・ハンドリングによる非同期プログラミングを簡単にしてくれます。
エラーハンドリングとZone
Future
の基本はthen
とcatchError
です。非同期処理が完了した時、値かエラーのどちらかが必ず返されます。値が返ってきた場合はthen
、エラーが返ってきた場合はcatchError
がコールバックされます。then
の内部でエラーが投げられた場合もcatchError
にジャンプします。
void main() {
var future = new Future(() => 1);
future.then((_) => throw "error")
.catchError((e) => print(e));
}
error
しかしFuture
が入れ子になっていたらどうなるでしょうか?
void main() {
var future = new Future(() => 1);
future.then((_) {
var innerFuture = new Future(() {
throw "innerError";
});
throw "error";
})
.catchError((e) => print(e));
}
error
Unhandled exception:
Uncaught Error: innerError
Stack Trace:
...
これを実行すると、"innerError"
はprint
されず、ハンドリングされていないことがわかります。Future
の非同期処理はそれぞれ固有の Zone という範囲内で行われます。catchError
はそのFuture
が属しているZoneの内部で発生したエラーしかハンドルすることができません。ですので、"innerError"
をハンドルするには次のように書く必要があります。
void main() {
var future = new Future(() => 1);
future.then((_) {
var innerFuture = new Future(() {
throw "innerError";
}).catchError((e) => print(e));
throw "error";
})
.catchError((e) => print(e));
}
しかし、runZoned
を使うと、入れ子になったFuture
も全て同じZoneで実行させることができます。runZoned
の第一引数に渡した関数内で発生したエラーは全てonError
引数でハンドルされます。
void main() {
var future = new Future(() => 1);
runZoned(() {
future.then((_) {
var innerFuture = new Future(() {
throw "innerError";
});
throw "error";
});
}, onError: (e) => print(e));
}
error
innerError
このようにしてDartでは非同期プログラミングでもtry-catchのようにエラーハンドリングすることができます。