本記事は下記リンクのDark公式ページのチュートリアルを学習したメモです。

Asychronous programming

前回の続き

try-catch

通常の関数と同じく、async関数もtry-catchで使える。

次のサンプルコードを見てみよう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void main() async {
  await printOrderMessage();
}

Future<String> fetchUserOrder() {
  var str = Future.delayed(
    const Duration(seconds: 4),
    () => throw 'さっさと注文しろ!',
  );
  return str;
}

Future<void> printOrderMessage() async {
  try {
    print('ご注文は何でしょう?');
    var order = await fetchUserOrder();
    print(order);
  } catch(err) {
    print('エラーが発生しました:$err');
  }
}
1
2
ご注文は何でしょう?
エラーが発生しました:さっさと注文しろ!

fetchUserOrderでは今まで待機後、文字列を返却していたが、今回はエラーをスローしている。

printOrderMessageではfetchUserOrderを待っているが、エラーがスローされたのでcatchブロックが実行される。

注文を聞いて4秒後にエラーメッセージが表示されるようになる。

Stream

Futureは未来に返却されるものと学んだ、Streamは何だろう。

辞書では連続、流れとか説明されている。

Futureで宣言された関数はawaitにより関数が完了sれるのを待って返却値をもらう。

当たり前だが、1回のみだ。

Streamは関すが完了されるまで何回も返却値をもらえる。

最初はどういうこと?と思った。

これからコードで確認してみよう。

まずはlistenというものを理解する必要がある。

Streamで返却する関数の返却値を待ってるようなものだ。

まずはStreamを持っているStreamControllerを使ってlistenの概念を理解してみよう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'dart:async';

void main() {
  final ctl = StreamController();
  final stream = ctl.stream;
  
  final listener1 = stream.listen(
    (val) {
      print('Listener 1: $val');
    }
  );
  
  ctl.sink.add(1);
  ctl.sink.add(2);
  ctl.sink.add(3);
  ctl.sink.add(4);
  ctl.sink.add(5);
}
1
2
3
4
5
Listener 1: 1
Listener 1: 2
Listener 1: 3
Listener 1: 4
Listener 1: 5

StreamControllerは制御可能なStreamを持っているコントローラーだ。

StreamControllerのstreamからlistenerを設定して何か流れたときに実行される関数を渡している。

今回は流された値を出力する簡単な機能にしている。

その次はコントローラーから値を流すとlistenerとして設定した関数が実行されることになる。

次のような階乗を返却する関数があるとしよう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
  print(facto(0));
  print(facto(1));
  print(facto(2));
  print(facto(3));
  print(facto(4));
}

int facto(int number) {
  var result = 1;
  for (int i = 1; i <= number; i++) {
    result *= i;
  }
  return result;
}
1
2
3
4
5
1
1
2
6
24

計算される過程の途中の値ももらえたくて次のようにしても思ったような結果にはならない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 'dart:async';

void main() async {
  print(await facto(0));
  print(await facto(1));
  print(await facto(2));
  print(await facto(3));
  print(await facto(4));
}

Future<int> facto(int number) async {
  var result = 1;
  for (int i = 1; i <= number; i++) {
    result *= i;
    Future.delayed(
      const Duration(seconds: 1),
      () => result,
    );
  }
  return result;
}

returnが実行される時点で関数は終わりだし、待機している間に最後のreturnで関数は終わってしまう。

Streamを使ってみよう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'dart:async';

void main() {
  for(var i = 0 ; i < 5; i++) {
    facto(i).listen((val) {
      print('facto($i): $val');
    });
  }
}

Stream<int> facto(int number) async* {
  var result = 1;
  for (int i = 1; i <= number; i++) {
    result *= i;
    yield result;
    await Future.delayed(const Duration(seconds: 1));
  }
}
1
2
3
4
5
6
7
8
9
10
facto(1): 1
facto(2): 1
facto(3): 1
facto(4): 1
facto(2): 2
facto(3): 2
facto(4): 2
facto(3): 6
facto(4): 6
facto(4): 24

何が起きたか、考えてみよう。

facto関数の返却値をStreamに変えた。Streamに変えたらasyncには*をつけてあげないといけない。

returnは見えないでyieldキーワードが出てきた。

returnは関数が終わってしまうのでyieldキーワードでStreamに値を流している。

その次は1秒間待機だ。

mainはどうだろう。ただ、0から4までfactoにパラメータとして渡して実行している。

実行しているが、listenをつけているのでStreamに値が流されるたびにlistenに設定した関数が実行される。

じゃあ、最初は0を入れたのでfactoのforにも入らず終わってしまった。

次は1、1を流して1秒待つ、その間に2の1が流されて1秒待つ、その間3の1が流されて1秒待つ、その間4の1が流されて1秒待つ。

その次1はforが終わったので終わり、パラメータ2は2を流して1秒待つ、、、、

文で説明すると長くなるので一旦ここまで

上記のサンプルでは各呼び出しの結果が混ざって出力されているのがわかる。

もし、一つの呼び出しが終わってから次に進みたい場合はどうしたら良いか。

awaitと同じようなこともできる。

少し形は変わったが、yield*でawaitと同様の効果が得られる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import 'dart:async';

void main() {
  allStream().listen((val) {
    print(val);
  });
}

Stream<int> allStream() async* {
  yield* facto(1);
  yield* facto(2);
  yield* facto(3);
  yield* facto(4);
}
Stream<int> facto(int number) async* {
  var result = 1;
  print('facto($number)');
  for (int i = 1; i <= number; i++) {
    result *= i;
    yield result;
    await Future.delayed(const Duration(seconds: 1));
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
facto(1)
1
facto(2)
1
2
facto(3)
1
2
6
facto(4)
1
2
6
24

最後に

Dartの記事はしばらくアップしないと思う。一旦、ここで終わりにして次はFlutterを中心に記事をアップしていく。

ブログにアウトプットする前にはコソコソFlameでゲーム作ろうとしてたけど、Flameは結局FlutterのWidgetで通常のWidgetと一緒に使ったり、通常のWidget制御するようにFlameを制御したりするのをみてやはりある程度Flutterまで理解した上でFlame進めた方が良いと思っている。

タグ:

カテゴリー:

更新日時:

コメントする