Zones (Dart公式ドキュメントの翻訳)


> 原文: https://www.dartlang.org/articles/zones/
> 2014/12/14時点のものを翻訳
> ライセンスは[www.dartlang.orgのもの](https://github.com/dart-lang/www.dartlang.org/blob/master/LICENSE)に従います
> 英語難しいので間違いあったらご指摘ください

## Asynchronous dynamic extents (非同期の動的な拡張）

_著者: Florian Loitsch と Kathy Walrath_
_2014年3月_

この記事では `dart:async` ライブラリの zone 関連のAPIについて、トップレベル関数の `runZoned()` 関数に注目して説明します。この記事を読む前に、[Futureとエラーハンドリング](https://www.dartlang.org/articles/futures-and-error-handling/) で取り上げられているテクニックについて知っておく必要があります。

現在最もzoneが使われているのは、非同期に実行されるコードによるエラーの、エラーハンドリングを目的としています。例えば、シンプルなHTTPサーバーの場合、次のように使用します。

```dart
runZoned(() {
  HttpServer.bind('0.0.0.0', port).then((server) {
    server.listen(staticFiles.serveRequest);
  });
},
onError: (e, stackTrace) => print('Oh noes! $e $stackTrace'));
```
zoneを有効にしてHTTPサーバーを実行すると、（致命的でない）エラーがをサーバーの非同期のコードでキャッチしていないにもかかわらず、アプリケーションは動作し続けます。

> **API note**: この利用例は、常にzoneが必須なわけではありません。我々は、キャッチしてないエラーを拾う事ができるAPIをisolateが持つようになる事を期待しています。

zoneは次のようなタスクを可能にします：

* 前述の例のように、非同期のコードから投げられた例外が原因で終了しないように、アプリケーションを保護します。
* 個々のゾーンと、（zoneのローカルな）データを紐付けます。
* 例えば`print()`や`scheduleTask()`といった、制限のあるメソッドを、一部、あるいは全てのコード上で上書きする事ができます。
* コードがzoneに出たり入ったりする度に何らかの操作（例えばタイマーの開始や終了、スタックトレースの保存など）を実行する事ができます。

あなたは他の言語で、zone と似たものを見たことがあるのではないでしょうか。NodeJSの Domains はDartのzoneのインスピレーションになりました。Javaのスレッドローカルストレージはいくつかの類似点を持っています。[このビデオ](http://www.youtube.com/watch?v=3IqtmUscE_U)で説明されている、Brian FordのDartのzoneのJavascriptへのポートである[zone.js](https://github.com/btford/zone.js/)が一番似ています。

## Zoneの基本

zoneは、呼び出しの非同期で動的な拡張を意味しています。それは部分的な呼び出しとして振る舞い、かつ、他動詞的にコードに登録されたコールバックを非同期で処理します。

例えばHTTPサーバーの例では、`bind()`や`then()`や、`then()`のコールバックは全て、`runZoned()`で生成された、同じzone上で実行されます。

次の例では3つの異なるzoneでコードが実行されます。`zone #1`（ルートzone）と、`zone #2`と、`zone #3`の3つです。

![Zones___Dart__Structured_web_apps-6.png](https://qiita-image-store.s3.amazonaws.com/0/3865/07867c1d-e7d6-1536-5752-9f3fca79ecf0.png "Zones___Dart__Structured_web_apps-6.png")

> 訳注：サンプルコード上の背景色に意味があり、Qiitaで表現できないので画像を貼り付けています。
> **青色が zone #1**（ルートzone）、 **緑色が zone #2** 、 **黄色が zone #3** を表しています。

次の図ではコードの実行順序に加えて、どのzoneでコードが実行されるのかを表しています。

![trace.png](https://qiita-image-store.s3.amazonaws.com/0/3865/2c0dfaac-ccde-c72f-1baf-976bd916fa49.png "trace.png")

`runZoned()`を呼び出す度に、新しいzoneが生成され、コードはそのzoneで実行されます。(`baz()`を呼び出すように）コードがタスクをスケジュールする時、そのタスクはスケジュールされたzoneで実行されます。例えば、(`main()`の最後の行の) `qux()` の呼び出しは、`zone #2` で実行される `future` に、渡されているにも関わらず、`zone #1` (ルートzone) で実行されます。


子のzoneが親のzoneを置き換えるわけではありません。むしろ、新しいzoneは周りのzoneの中にネストされています。例えば `zone #2`は`zone #3`を含んでいますし、`zone #1`(ルートzone)は`zone #2`と`zone #3`の両方を含んでいます。

全てのDartのコードはルートzoneで実行されます。他のネストした子のzoneの中で実行される事もありますが、それは少なくとも常にルートzoneで実行されています。

## 非同期のエラーをハンドリングする

zone でもとも利用される機能のひとつに、非同期に実行されるコードでcatchされてないエラーをハンドリングできる事があります。同期的に実行されるコードにおいてのtry-catchと似たコンセプトです。

catchされてないエラーは、catch句でハンドリングされてない例外をthrowさせるコードが原因で発生します。他には、`new Future.error()`を呼び出したり、Completerの`completeError()`メソッドによって、非同期の例外を発生させる事ができます。

`runZoned()` の `onError` 引数にzoneのエラーハンドラ(非同期エラーハンドラ)を設定する事で、そのzoneで発生したあらゆるcatchされてないエラーをハンドリングする事ができます。例えば：

```dart
runZoned(() {
  Timer.run(() { throw '普段はプログラムを殺します'; });
}, onError: (error, stackTrace) {
  print('catchしてないエラー: $error');
});
```

ここで示したコードには例外を発生させる(`Timer.run()`に渡してある)非同期のコールバックがあります。通常であればこの例外は、catchされてない例外であり、トップレベルに到達します（スタンドアローンなDart実行であれば、実行中のプロセスを殺します）。しかし、zoneのエラーハンドラによってこのエラーはエラーハンドラに渡され、プログラムをシャットダウンしません。

try-catchとzoneのエラーハンドラにおいて注目すべき違いは、catchsされないエラーが発生した後もzoneが実行を続けるという事です。そのzoneに他の非同期なコールバックがスケジュールされていた場合、それらは実行されます。つまり、zoneのエラーハンドラは複数回呼び出される可能性があります。


また、（エラーハンドラを持つ）zoneは、子（あるいは、その他の子孫）が起因のエラーを処理する可能性がある事に注意してください。どこでエラーが処理されるのかは単純なルールで、(`then()`か`catchError()`を利用している)`Future`の変換シーケンスで決定されます：

> `Future`のエラーが、エラーゾーンの境界を越えて連鎖する事はありません。

エラーがエラーゾーンの境界に到達した場合、その時点でハンドリングされてないエラーとして処理されます。

> 訳注：以下「エラーゾーン」という単語が出てきますが、ほとんどのケースで「onErrorが設定してあるzone」というニュアンスで使われているようです。

### 例：エラーはエラーゾーンを横断する事ができない

次の例では、最初の行で発生したエラーはエラーzoneを横断する事ができません。

```dart
var f = new Future.error(499);
f = f.whenComplete(() { print('runZonedの外側'); });
runZoned(() {
  f = f.whenComplete(() { print('エラーゾーンが無いzoneの内側'); });
});
runZoned(() {
  f = f.whenComplete(() { print('エラーゾーンが有るzoneの内側 (呼び出されない)'); });
}, onError: print);
```

実行時の出力は以下のようになるでしょう：

```
runZonedの外側
エラーゾーンが無いzoneの内側
Uncaught Error: 499
Unhandled exception:
499
...stack trace...
```

`runZoned()`の`onError`引数を削除すると、次のような出力になるでしょう：

```
runZonedの外側
エラーゾーンが無いzoneの内側
エラーゾーンが有るzoneの内側 (呼び出されない)
Uncaught Error: 499
Unhandled exception:
499
...stack trace...
```

zoneかエラーゾーンのどちらかを削除すると、エラーがさらに伝わってゆくので注意してください。

エラーがエラーゾーンの外側で発生したため、スタックトレースが表示されます。コードスニペット全体にエラーゾーンを追加する事で、スタックトレースを回避する事ができます。

### 例：エラーはエラーゾーンを離れられない

前述のコードはエラーがエラーゾーンを横断する事ができない事を示しました。同様に、エラーはエラーゾーンの外に越える事ができません。次の例をじっくり見てみましょう：

```dart
var completer = new Completer();
var future = completer.future.then((x) => x + 1);
var zoneFuture;
runZoned(() {
  zoneFuture = future.then((y) => throw 'zoneの内側');
}, onError: (error) {
  print('エラーを拾いました: $error');
});
zoneFuture.catchError((e) { print('ここには来ません'); });
completer.complete(499);
```

> 訳注: 上記の実行結果は以下になります
> `エラーを拾いました: zoneの内側`

`Future`のチェーンは`catchError()`で終わったが、非同期なエラーはエラーゾーンを離れられません。かわりに(onErrorが指定されている）zoneのエラーハンドラがエラーを処理します。結果、`zoneFuture`は完了していません。エラーでも、値でも。

> 訳注: 実行結果は`エラーを拾いました: zoneの内側`になります。
> うまく翻訳できませんでしたが、言わんとしてるところは、`runZoned()`内のfutureのthenで発生した例外が`zoneFuture.catchError`に到達せずに、`onError`で終了しているという点が重要なポイントです。
> また、[Completer](https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:async.Completer)は、Futureを扱いやすくするラッパーみたいなクラスです。たぶん。

## zoneをstreamと一緒に使う

zoneとstreamのルールは、Futureよりも簡単です：

> 変換(transformation)やその他のコールバックはstreamがlistenしてるzoneで実行されます。

このルールは「streamはlistenしてる間副作用を持つべきではない」というガイドラインに従ってます。同期的コードにおける似たような事に、「イテレータの、値を求められるまで評価しない振る舞い」といったものがあります。

### 例：streamをrunZoned()と一緒に使う

次にstreamを`runZoned()`と一緒に使う例をあげます：

```dart
var stream = new File('stream.dart').openRead()
    .map((x) => throw 'Callback throws');

runZoned(() { stream.listen(print); },
         onError: (e) { print('Caught error: $e'); });
```

コールバックにより投げられた例外は`runZoned()`のエラーハンドラによってcatchされます。出力は、次のようになります。

```
Caught error: Callback throws
```

この出力が示すように、コールバックは、`map()`が呼び出されているzoneではなく、listenしてるzoneに関連付けられています。

## zoneのローカルバリューを保存する

static変数を利用したいが、複数の同時に実行される計算が干渉するなどの理由で利用できなかった場合、zoneのローカルバリューを使う事を検討してください。デバッグを助けるためにzoneローカルバリューを追加しても良いでしょう。他の利用例としては、HTTPリクエストで、zoneローカルバリューにあるユーザーIDと認証用のトークンを扱うなどです。

`runZoned()`の`zoneValues`引数で、新しいzoneに値を保存する事ができます。

```dart
runZoned(() {
  print(Zone.current[#key]);
}, zoneValues: { #key: 499 });
```

zoneローカル変数を参照するために、zoneのインデックス演算子と値のキー：　`[key]` を利用します。キーは[`Symbol`](https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:core.Symbol)でなければなりません。通常は、キーはシンボルリテラル `#identifier` です。

> **API note**: [リビジョン34248のリリース](https://code.google.com/p/dart/source/detail?r=34248)になると、キーはシンボルに限定されなくなります。

キーに紐付けられたオブジェクトを変更する事はできませんが、オブジェクトを操作する事はできます。例えば次のコードは、zoneのローカルリストにアイテムを追加しています：


```dart
runZoned(() {
  Zone.current[#key].add(499);
  print(Zone.current[#key]); // 出力は「[499]」になります
}, zoneValues: { #key: [] });
```

zoneはzoneローカルバリューを親のzoneから継承し、ネストしたzoneが既存の値をうっかり失ったりはしません。ただ、ネストしたzoneは、親の値をシャドーイング（訳注：[ASCII.jpデジタル用語辞典](http://yougo.ascii.jp/caltar/%E3%82%B7%E3%83%A3%E3%83%89%E3%83%BC%E3%82%A4%E3%83%B3%E3%82%B0)）する事ができます。

> **Important**: キーにはユニークな、あまり他のライブラリと競合しそうにないオブジェクトを使うようにしてください。

### 例：デバッグログにzoneのローカルバリューを使う

`foo.txt` と `bar.txt` の2つのファイルがあり、この2つの全ての行を print したい場合、プログラムは次のようになるでしょう：


```dart
import 'dart:async';
import 'dart:convert';
import 'dart:io';

Future splitLinesStream(stream) {
  return stream
      .transform(ASCII.decoder)
      .transform(const LineSplitter())
      .toList();
}

Future splitLines(filename) {
  return splitLinesStream(new File(filename).openRead());
}
main() {
  Future.forEach(['foo.txt', 'bar.txt'],
                 (file) => splitLines(file)
                     .then((lines) { lines.forEach(print); }));
}
```

このプログラムは動きますが、たとえば、どのファイルのどの行が来ていて、`splitLinesStream()`の引数にファイル名を追加する事はできません。zoneローカルバリューを使うと、stringの返り値にファイル名を追加する事ができます。（追加した行をハイライトしています）

> 訳注: Qiitaではハイライトを表現できないため、コメントを追加しています。`.map()` と `runZoned()`の部分の合計3行が追加されています。

```dart
import 'dart:async';
import 'dart:convert';
import 'dart:io';

Future splitLinesStream(stream) {
  return stream
      .transform(ASCII.decoder)
      .transform(const LineSplitter())
      .map((line) => '${Zone.current[#filename]}: $line') //訳注:追加された行です
      .toList();
}

Future splitLines(filename) {
  return runZoned(() { //訳注：追加された行です
    return splitLinesStream(new File(filename).openRead());
  }, zoneValues: { #filename: filename }); //訳注：追加された行です
}

main() {
  Future.forEach(['foo.txt', 'bar.txt'],
                 (file) => splitLines(file)
                     .then((lines) { lines.forEach(print); }));
}
```

新しいコードが関数のシグネチャ（訳注：参考/[メソッドのシグネチャ（signature）とメソッドの構文（syntax）の違い](http://blog.masahiko.info/entry/2008/06/17/010104)）を変更したり、`splitLines()`から`splitLinesStream()`にファイル名を渡していない事に注意してください。かわりに、非同期のコンテキストで動作するstatic変数と同様の機能を実装するためにzoneのローカルバリューを利用しています。

## 関数のオーバーライド

`runZoned()`の`zoneSpecification`引数を使う事で、zoneに管理されたかたちで関数がオーバーライドされます。引数の値でオーバーライドする事ができる`ZoneSpecification`のオブジェクトの機能は次の通りです：

* 子のzoneをフォークする事ができる
* zoneにコールバックを登録して実行できる
* タイマーとmicrotaskをスケジューリングできる
* 非同期のcatchされてないエラーを処理できる（`onError`はこのためのショートカットです）
* printできます

### 例：printをオーバーライドする

関数をオーバーライドする簡単な例として、zoneのプリントを無視します：


```dart
import 'dart:async';

main() {
  runZoned(() {
    print('無視されるでしょう');
  }, zoneSpecification: new ZoneSpecification(
    print: (self, parent, zone, message) {
      // メッセージを無視します。
    }));
}
```

フォークされたzoneの中では、`print()`関数は単純にメッセージを破棄するprintにオーバーライドされています。(`scheduleMicrotask()`や`Timer`コンストラクタのように）`print()`はその動作を現在のzone (`Zone.current`)で行っているため、`print`のオーバーライドが可能です。

### 委譲(Delegate)とインターセプタのための引数

printの例が示すように、インターセプタは対応するメソッドの引数に、Zoneクラスの定義されている3つの引数を追加します。例えばZoneの`print()`メソッドはひとつの引数（`print(String lien)`）を持っています。`ZoneSpecification`で定義されたインターセプターの`print()`は4つの引数（`print(Zone self, ZoneDelegate parent, Zone zone, String line)`）を持っています。

この3つのインターセプタの引数は常に同じ順番で、他の引数の前にあります。


<dl>
	<dt><strong>self</strong></dt>
	<dd>コールバックを処理するzoneです。</dd>
	<dt><strong>parent</strong></dt>
	<dd>親のzoneを表す `ZoneDelegate` です。親のzoneに対しての操作を転送するために使います。</dd>
	<dt><strong>zone</strong></dt>
	<dd>作業の元となったzoneです。いくつかの作業においてはどのzoneでその作業が発生したのか知る必要があります。例えば、`zone.fork`(の仕様)は、新しいzoneをzoneから作る必要があります。他の例では、`scheduleMicrotask()`を他のzoneに委譲する場合、元のzoneはmicrotaskを実行するひとつでなければなりません。</dd>
</dl>

インターセプタが親にメソッドを委譲するとき、親(`ZoneDelegate`)バージョンのメソッドは、呼び出し元となったzoneを引数にひとつだけ追加します。例えば、`ZoneDelegate`上の`print()`メソッドのシグネチャは `print(Zone zone, String line)`です。

他のインターセプトができるメソッドである`scheduleMicrotask()`の例を示します：

| 定義されているところ | メソッドのシグネチャ |
|:--------------|:-----------------|
| Zone | `void scheduleMicrotask(void f())` |
| ZoneSpecification | `void scheduleMicrotask(Zone self, ZoneDelegate parent, Zone zone, void f())` |
| ZoneDelegate | `void scheduleMicrotask(Zone zone, void f())` |


### 例：親のzoneへの委譲

親のzoneにどのように委譲すればよいのか例を示します：

```dart
import 'dart:async';

main() {
  runZoned(() {
    var currentZone = Zone.current;
    scheduleMicrotask(() {
      print(identical(currentZone, Zone.current));  // 「true」が表示されます
    });
  }, zoneSpecification: new ZoneSpecification(
    scheduleMicrotask: (self, parent, zone, task) {
      print('zoneの内側でscheduleMicrotaskは呼ばれています');
      // タスクを実行する事ができるように、
      // 元の「zone」は、親に渡す必要があります。
      parent.scheduleMicrotask(zone, task);
    }));
}
```

> 訳注：上記の実行結果は以下のようになります。
> `zoneの内側でscheduleMicrotaskは呼ばれています`
> `true`

### 例：zoneに入る時と、zoneから出る時にコードを実行する

非同期のコードの実行時間を知りたい時があります。zoneにコードを置く事で、zoneに入る度にタイマーを起動し、zoneから出る度にタイマーを止める事で、これを行う事ができます。

`ZoneSpecification`に`run*`パラメータを渡すする事で、zoneが実行するコードを指定する事ができます。

> **API note**: 将来的にはzoneのコードを挟`onEnter`/`onLeave` APIという、一般的なケースにおいての代替手段をzoneが提供するかもしれません。詳細は[issue 17532](https://code.google.com/p/dart/issues/detail?id=17532)を見てください。

`run*`パラメータ（`run`と`runUnary`と`runBinary`）は、zoneがコードの実行を要求されるたびに実行するコードを指定します。これらのパラメータは、それぞれ0個の引数、1つの引数、2つの引数のコールバックで動作します。runパラメータは`runZoned()`が呼ばれた直後に実行され、初期の同期的なコードのために動作します。

`run*`を使ったプロファイリングの例です：

```dart
final total = new Stopwatch();
final user = new Stopwatch();

final specification = new ZoneSpecification(
  run: (self, parent, zone, f) {
    user.start();
    try { return parent.run(zone, f); } finally { user.stop(); }
  },
  runUnary: (self, parent, zone, f, arg) {
    user.start();
    try { return parent.runUnary(zone, f, arg); } finally { user.stop(); }
  },
  runBinary: (self, parent, zone, f, arg1, arg2) {
    user.start();
    try {
      return parent.runBinary(zone, f, arg1, arg2);
    } finally {
      user.stop();
    }
  });

runZoned(() {
  total.start();
  // ... 同期的に実行されるコード ...
  // ... 非同期で実行されるコード ...
    .then((...) {
      print(total.elapsedMilliseconds);
      print(user.elapsedMilliseconds);
    });
}, zoneSpecification: specification);
```

このコードでは、それぞれの`run*`をオーバーライドするだけで、ユーザータイマーを開始し、指定された関数を実行し、ユーザータイマーを停止させています。

### 例：コールバックのハンドリング

`zone`で非同期に実行されるコールバックのコードを、変更、あるいはラップするために`ZoneSpecification`の`register*Callback`パラメータを提供しています。`run*`パラメータのように、`register*Callback`は、`registerCallback`（引数が無いコールバック）、`registerUnaryCallback`（1つの引数)、`registerBinaryCallback`（2つの引数）の3つの形式があります。


次のは、コードが非同期の中に消える前にスタックトレースを保存する例です：

```dart
import 'dart:async';

get currentStackTrace {
  try {
    throw 0;
  } catch(_, st) {
    return st;
  }
}

var lastStackTrace = null;

bar() => throw "in bar";
foo() => new Future(bar);

main() {
  final specification = new ZoneSpecification(
    registerCallback: (self, parent, zone, f) {
      var stackTrace = currentStackTrace;
      return parent.registerCallback(zone, () {
        lastStackTrace = stackTrace;
        return f();
      });
    },
    registerUnaryCallback: (self, parent, zone, f) {
      var stackTrace = currentStackTrace;
      return parent.registerUnaryCallback(zone, (arg) {
        lastStackTrace = stackTrace;
        return f(arg);
      });
    },
    registerBinaryCallback: (self, parent, zone, f) {
      var stackTrace = currentStackTrace;
      return parent.registerBinaryCallback(zone, (arg1, arg2) {
        lastStackTrace = stackTrace;
        return f(arg1, arg2);
      });
    },
    handleUncaughtError: (self, parent, zone, error, stackTrace) {
      if (lastStackTrace != null) print("last stack: $lastStackTrace");
      return parent.handleUncaughtError(zone, error, stackTrace);
    });

  runZoned(() {
    foo();
  }, zoneSpecification: specification);
}
```

まずは例を実行してみてください。`foo()`が同期的に呼ばれてから、`foo()`に含まれる「最後のスタック」のスタックトレース(`lastStackTrace`)が見えると思います。次のスタックトレース(`stackTrace`)は`foo()`ではなく`bar()`の非同期のコンテキストからのものです。

### 非同期のコールバックの実装

非同期のAPIを実装する場合も、すべてのzoneで対応する必要は無いかもしれません。例えば今のzoneを追跡し続けるために `dart:io`ライブラリを予想するかもしれませんが、代わりに`Future`や`Stream`のような`dart:async`ライブラリのクラスのzoneの処理に依存しています。

明示的にzoneを扱う場合、全ての非同期なコールバックを登録し、それぞれのコールバックが、登録されたzoneで呼び出される事を確認する必要があります。`Zone`のヘルパーメソッドである`bind*Callback`は、これを簡単に提供します。これは`register*Callback`と`run*`のためのショートカットで、それぞれのコールバックが`Zone`に登録され実行される事を確認します。

`bind*Callback`よりも細かいコントロールが必要な場合は、`register*Callback`と`run*`を使うと良いでしょう。また、エラーが発生した場合の、try-catchと`uncaughtErrorHandler`の呼び出しをラップする、`Zone`の`run*Guarded`メソッドを利用するとよいでしょう。

## まとめ

zoneは確かに非同期のコードで発生する、catchされない例外からコードを保護するのに役立ちますが、それだけでなくもっとたくさんの事ができます。データをzoneに関連づけたり、printやタスクスケジュールなどのコアな機能をオーバーライドしたりできます。zoneはより良いデバッグを可能にし、プロファイリングなどのために利用できるフックを提供してくれます。

### さらに詳しく

<dl>
	<dt><strong><a href="https://www.dartlang.org/articles/event-loop/">The Event Loop and Dart</a></strong></dt>
	<dd><code>Future</code>や<code>Timer</code>、<code>scheduleMicrotask()</code>を使ったタスクのスケジュールについて学びましょう（訳注:拙訳ですが<a href="http://qiita.com/takyam/items/6ad155678c95bba4047f">こちらも翻訳しています</a>）。</dd>
	<dt><strong>Zone-related API documentation</strong></dt>
	<dd><a href="https://api.dartlang.org/apidocs/channels/stable/#dart-async@id_runZoned"><code>runZoned()</code></a>、<a href="https://api.dartlang.org/apidocs/channels/stable/#dart-async.Zone"><code>Zone</code></a>、<a href="https://api.dartlang.org/apidocs/channels/stable/#dart-async.ZoneDelegate"><code>ZoneDelegate</code></a>、<a href="https://api.dartlang.org/apidocs/channels/stable/#dart-async.ZoneSpecification"><code>ZoneSpecification</code></a>のドキュメントを読みましょう。</dd>
	<dt><strong>stack_trace</strong></dt>
	<dd><code>stack_trace</code>ライブラリの<a href="https://api.dartlang.org/apidocs/channels/stable/#stack_trace/stack_trace.Chain"><code>Chain クラス</code></a>によって、よりより非同期に実行されるコードのスタックトレースを得られるでしょう。詳細は、pub.dartlang.orgの<a href="http://pub.dartlang.org/packages/stack_trace"><code>stack_traceパッケージ</code></a>を見てください。</dd>
</dl>

### さらに例を見る

zoneを使ったさらに複雑な例があります。

<dl>
	<dt><strong>The task_interceptor example</strong></dt>
	<dd><a href="https://github.com/dart-lang/dartlang.org/blob/master/src/tests/site/articles/zones/task_interceptor.dart"><code>task_interceptor.dart</code></a>の<code>toy zone</code>は、イベントループに屈せず、<code>scheduleMicrotask</code>や<code>createTimer</code>、<code>createPeriodicTimer</code>のDartのプリミティブな動作をシミュレートします。</dd>
	<dt><strong>stack_traceパッケージのソースコード</strong></dt>
	<dd><a href="http://pub.dartlang.org/packages/stack_trace">stack_traceパッケージ</a>は非同期のコードをデバッグするために、スタックトレースのチェーンを形成するzoneを使っています。エラーハンドリング、zoneローカルバリュー、コールバックといったzoneの機能を使っています。stack_traceのソースコードは、Dartプロジェクトの<a href="https://code.google.com/p/dart/source/browse/branches/bleeding_edge/dart/pkg/stack_trace/lib/#lib%2Fsrc"><code>pkg/stack_trace/lib/src</code></a>で見る事ができます。</dd>
	<dt><strong>dart:html と dart:async のソースコード</strong></dt>
	<dd>これらの2つのライブラリは、非同期コールバックの主要なAPIを実装しており、そのため、zoneを使っています。<a href="https://code.google.com/p/dart/wiki/GettingTheSource?tm=4">DartプロジェクトのSourceタブ</a>から、ソースコードの閲覧やダウンロードが可能です。</dd>
</dl>


_この記事をレビューしてくれたAnder JohnsenとLasse Reichsteinに感謝しています。_

------

翻訳ここまで --- 以下翻訳してみての感想

------

1. イマイチよく分かってないとこあるので翻訳間違ってるかも（勘違いして間違った事書いてるかも）
2. 気づいたら修正しますが、コメントでご指摘もらえると助かります
3. 積極的に関数のオーバーライドとかする事は余りなさそうな気もしつつ、そういう機能とかzoneの引数とか知らないと、ライブラリのコード読む時になんじゃこりゃってなってハマりそうなのでしれてよかった。
4. とりあえずは何も考えずに`runZoned()`で`onError`だけちゃんとつけるようにしとけばいいかな。