読者です 読者をやめる 読者になる 読者になる

Dart練習帳: Future/Completer

Dart

この記事はPromise型を撤廃してFuture型に統一していくよ、という記事だと思うのだが、"try it out"といいつつ全くドキュメントがないので、どういう人を対象読者にしているのかなぁとびっくりしてしまう。そこで、Dartのユニットテストを参考に粛々と練習する。

APIリファレンスはこれ。

Completer

最も単純と思われるパターン。Completerで処理を定義し、completeで処理を完了させる。Futureオブジェクトにアクセスするにはcompleter.futureで。

void main() {
  final completer = new Completer<String>();
  final transformedFuture = completer.future.transform((x) => "** $x **");

  // まだ完了しないので"Not Yet"が表示される
  print(transformedFuture.isComplete ? "Completed" : "Not Yet");

  // 完了させる
  completer.complete("Hello, Dart!");

  // "** Hello, Dart! **"
  print(transformedFuture.value);
}

Future.immediate

Future.immediateで即時完了させる。

void main() {
  final future = new Future<String>.immediate("Hello, Dart!");

  // immediateで即時完了しているので"Completed"が表示される
  print(future.isComplete ? "Completed" : "not yet");

  // futureが完了したらvalueに値を代入
  var value = null;
  future.then((x) => value = x);

  // "Hello, Dart!"が表示される
  print(value);
}

completeException

completer.completeExceptionで処理が完了したがエラーになったことを伝えられる。

void main() {
  final completer = new Completer<String>();
  final transformedFuture = completer.future.transform((x) => "** $x **");b

  // 処理中にエラーが発生した場合completeExceptionで結果を伝える
  completer.completeException(new Exception("Oh no!"));

  // completeExceptionの場合はisComplete=trueになる
  // "Completed"が表示される
  print(transformedFuture.isComplete ? "Completed" : "Not Yet");

  // "Exception: Oh no!"
  print(transformedFuture.exception);

  // 例外が発生した場合にfuture.valueにアクセスしようとすると
  // 実行時エラーになる
  print(transformedFuture.value);
}

transform時に例外を発生させる

処理は正常に完了したが、future.transform内で例外が発生した場合は、上と同じようにfuture.valueで値は取得できない。

void main() {
  final completer = new Completer<String>();

  // transformの処理内で例外を投げる
  final transformedFuture = completer.future.transform((x) { throw new Exception("Oh no!"); });

  completer.complete("Hello, Dart!");

  // "Exception: Oh no!"
  print(transformedFuture.exception);

  // transformedが例外を投げて失敗しているので実行時例外になる
  print(transformedFuture.value);
}

chainで逐次処理

処理Aに続いて処理B、処理Bに続いて処理C・・・のように逐次実行させる場合には、chainを使う。

void main() {
  final completerA = new Completer<String>();
  final completerB = new Completer<String>();
  final chainedFuture = completerA.future.chain((x) {
    return completerB.future;
  });

  // AもB未完了
  print(chainedFuture.isComplete ? "Chained Future is completed" : "Chained future isn't completed");

  // Aを完了させる
  completerA.complete("Hello, Completer A");

  // Bが未完了
  print(chainedFuture.isComplete ? "Chained Future is completed" : "Chained future isn't completed");

  // Bを完了させる
  completerB.complete("Hello, Completer B");

  // 完了
  print(chainedFuture.isComplete ? "Chained Future is completed" : "Chained future isn't completed");

  // "Hello, Completer B"が表示される
  print(chainedFuture.value);
}

上のバリエーションだが、処理Aの結果を処理Bの中で使う場合。

void main() {
  final completerA = new Completer<String>();
  final completerB = new Completer<String>();
  final transformedFuture = completerA.future.chain((x) {
    return completerB.future.transform((y) {
      return "$x, $y!";
    });
  });


  // Aを完了させる
  completerA.complete("Hello");

  // Bを完了させる
  completerB.complete("Dart");

  // "Hello, Dart!"
  print(transformedFuture.value);
}

Futures.wait

Futures.waitで複数の処理がすべて完了するのを待つことができる。

void main() {
  final completerA = new Completer<String>();
  final completerB = new Completer<String>();
  final completerC = new Completer<String>();

  final Future completer = Futures.wait([
    completerA.future,
    completerB.future,
    completerC.future
  ]);

  completer.then((List list) {
    for (var x in list) {
      print(x);
    }
  });


  // A, B, Cが未完了
  print(completer.isComplete ? "Completed" : "Not Yet");

  // Aを完了させる
  completerA.complete("Hello, Completer A");

  // B, Cが未完了
  print(completer.isComplete ? "Completed" : "Not Yet");

  // Bを完了させる
  completerB.complete("Hello, Completer B");

  // Cが未完了
  print(completer.isComplete ? "Completed" : "Not Yet");

  // Cを完了させる
  completerC.complete("Hello, Completer C");

  // すべて完了
  print(completer.isComplete ? "Completed" : "Not Yet");
}

future.thenはnullを返す

ちょっとハマったところとしては、future.thenはnullを返すので、JSDeferredjQuery DeferredのようにFluent interfaceで処理を書くことができないということ。すなわち、

void main() {
  final completer = new Completer<String>();
  final future = completer.future;

  future.then((x) {
    print("step 1: $x");
  });

  future.then((x) {
    print("step 2: $x");
  });

  future.then((x) {
    print("step 3: $x");
  });

  completer.complete("Hello, Darts!");
}

のように同一のfutureオブジェクトに対してthenを複数回呼ぶと期待したとおりに、

% dart fluent.dart
step 1: Hello, Darts!
step 2: Hello, Darts!
step 3: Hello, Darts!

のようにthenの処理がすべて実行されるが、残念ならが、以下のように書くことはできない。

void main() {
  final completer = new Completer<String>();
  final future = completer.future;

  future.then((x) {
    print("step 1: $x");
  })
    .then((x) {
    print("step 2: $x");
  })
    .then((x) {
    print("step 3: $x");
  });

  completer.complete("Hello, Darts!");
}