2013年5月8日水曜日

Future を使いこなす(1)

このエントリーをはてなブックマークに追加
Dart の Future についてはだいぶ前に解説(Dartで未来(Future)の処理を実装する)を書きましたが、それから使い方が少し変わっていますので、現時点での最新仕様(Milestone 4)をベースに改めて説明を書きました。

目次
  • Future を使いこなす(1)
    • Future とは何か(おさらい)
    • 基本の Future の書き方
    • エラー発生時の処理を記述する
  • Future を使いこなす(2)
    • Future を続けて処理する
    • 連結時のエラー処理を実装する
    • リストの要素を Future で順に処理する
    • 複数の Future をまとめて待つ
  • Future を使いこなす(3)
    • Future を返す関数を実装する
    • Future をテストする

Future とは何か(おさらい)

Future は Dart のライブラリとして提供されている、非同期処理のための機能です。結果がすぐに得られるかわからない処理を同期的に書いてしまうと、その処理が終わるまで次の処理がブロックされてしまいます。例えば Web からリソースを取得するとき、いつ結果が返ってくるか(あるいはエラーになるのか)を予測してプログラミングすることはほとんど不可能です。

それでは都合が悪いので、どのくらい時間がかかるかわからないような処理は非同期で実行し、結果が出た時にそれに対応する処理を実行させるということをよくやります。これを実現するための機能が Future です。

基本の Future の書き方

Future の機能は dart:async ライブラリで提供されていますので、プログラムの先頭で import する必要があります。
import ('dart:async');

void main() { /* ... */ }
同期的な関数は戻り値として処理結果を返します。エラーが発生した場合には例外を送出します。
// 同期的な関数
String concat(String a, String b) {
  return a + b;
}

void main() {
  // Hello world
  print(concat('Hello ', 'world'));
}
concat 関数から戻った時点で print 関数へ渡す結果が出ています。これに対して非同期的な関数では、関数から戻った時点では結果が出ているかどうか分かりません。そのような場合、Dart の関数は Future のインスタンスが返るようになっています。処理が終わると Future#then で指定した関数がコールバックされますので、そこに結果の処理を記述します。
import ('dart:async');

// 非同期的な関数
Future<String> asyncTask() { /* ... */ }

void main() {
  // Future を受け取る。
  var f = asyncTask();

  // Future#then に完了時の処理を書く。
  f.then((String s) => print(s));
}
実践的なプログラミングでは次のように短く書かれます。
import ('dart:async');

Future<String> asyncTask() { /* ... */ }

void main() {
  asyncTask().then(print);
}

エラー発生時の処理を記述する

非同期処理の中でエラーが発生した時や、then で登録したコールバック関数が例外を送出した時には、then の onError 引数に指定した関数が呼ばれます。
import ('dart:async');

Future<String> asyncTask() {
  // 非同期処理中にエラーが発生したり...
}

void main() {
  asyncTask().then(
    (s) {
      // コールバック関数内で例外が発生すると...
    },
    // onError が呼び出される。
    onError: (e) => print('ERROR: $e'));
}
あるいは Future#catchError を使って次のように書くこともできます。
import ('dart:async');

Future<String> asyncTask() { /* ... */ }

void main() {
  asyncTask()
    .then(print)
    .catchError((e) => print('ERROR: $e'));
}
エラーの処理では、エラーの型や内容によって処理を分けることがよくあります。Future#catchError では test 引数に bool 型の戻り値を持つ関数を渡し、そのコールバック関数でエラーを処理するかどうかを決められます。test 引数で指定した関数が true を返すとコールバック関数が呼び出され、false を返すと呼び出されません。
import ('dart:async');

Future<String> asyncTask() { /* ... */ }

void main() {
  asyncTask()
    .then(print)
    // int の場合はこちら
    .catchError((e) => print('ERROR No.$e'),
                test: (e) => e is int)
    // String の場合はこちら
    .catchError((e) => print('ERROR Message: $e'),
                test: (e) => e is String);
}
then と catchError を使った書き方は、同期処理における try-catch 構文に相当します。非同期処理と then で登録する関数は try ブロック、catchError で登録する関数は catch ブロックの処理に対応しています。そして finally に相当する Future#whenComplete も用意されています。
import ('dart:async');

Future<String> asyncTask() { /* ... */ }

void main() {
  asyncTask()
    .then(print)
    .catchError((e) => print('ERROR No.$e'),
                test: (e) => e is int)
    .catchError((e) => print('ERROR Message: $e'),
                test: (e) => e is String)
    .whenComplete(() => print('Completed.'));
}
正常に終了した場合は then と whenComplete、エラーが発生した場合は catchError と whenComplete で登録した関数が順に呼ばれます。

Future を使いこなす(2) へ続く

0 件のコメント:

コメントを投稿