Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Dartにおける非同期プログラミングとエラーハンドリング

More than 5 years have passed since last update.

この記事はバージョン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は上記のような非同期プログラミングを簡潔に記述できるようにしてくれます。例えば上のコールバックはasyncAFutureを返すようにすると次のように書き換えられます。

asyncA() // returns a Future
  .then(asyncAToB)
  .then(asyncBToC)
  .then(asyncSetC)
  .then((_) => print("done");

FutureはEcmaScriptでいうPromiseであり、コールバック・ハンドリングによる非同期プログラミングを簡単にしてくれます。

エラーハンドリングとZone

Futureの基本はthencatchErrorです。非同期処理が完了した時、値かエラーのどちらかが必ず返されます。値が返ってきた場合はthen、エラーが返ってきた場合はcatchErrorがコールバックされます。thenの内部でエラーが投げられた場合もcatchErrorにジャンプします。

void main() {
  var future = new Future(() => 1);
  future.then((_) => throw "error")
  .catchError((e) => print(e));
}
stdout
error

しかしFutureが入れ子になっていたらどうなるでしょうか?

void main() {
  var future = new Future(() => 1);

  future.then((_) {
    var innerFuture = new Future(() {
      throw "innerError";
    });
    throw "error";
  })
  .catchError((e) => print(e));
}
stdout
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)); 
}
stdout
error
innerError

このようにしてDartでは非同期プログラミングでもtry-catchのようにエラーハンドリングすることができます。

lacolaco
I play Angular and pray for Angular. No Breaking Changes No Life.
https://lacolaco.net
classi
学校の先生・生徒・保護者向けのB2B2Cの学習支援Webサービス「Classi(クラッシー)」 を開発・運営している会社です。
https://classi.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away