【Flutter】httpなどの非同期処理をasync/awaitで見やすくしてみる

Dart

概要

httpを始めとする処理を行う場合、多くは非同期処理になります。
通信を行う処理と通信が成功/失敗した場合の処理をそれぞれ記述します。
割と多く出て来る記述だと思うのでなるべく分かりやすく綺麗に書きたいですよね。

通信処理を何も考えず書いてみよう

通信の基本処理

import 'packages:http/http.dart' as http;

void fetchSomethingRequest() {
  const url = '...';
  http.get(url);
}

解説

シンプルですね、特に解説がなくてもやることは想像出来ます。

通信が成功した時の処理

import 'dart:convert';

import 'packages:http/http.dart' as http;

void fetchSomethingRequest() {
  const url = '...';
  http.get(url).then((response) {
    if (response.statusCode != 200) {
      throw Exception('Something occurred.');
    }
    final decodedBody = json.decode(response.body) as Map<String, dynamic>;
    setState(() {
      _item = SomeObject(
        id: decodedBody['id'],
        title: decodedBody['title'],
        // ...
      );
    });
    // ...
  });
}

解説

通信後の挙動をthenブロックの中に書いてみました。
httpパッケージの各主要メソッドはFuture<Response>を返却する様になっています。
Futureクラスは非同期処理の結果を表すクラスです。
このFutureにはthenメソッドがあり、レスポンスを受けた時の処理を書く事が出来ます。

書き方にもよりますが、ここにnotifyしたりとか様々な処理が入って来るので
もっとみづらくなる可能性もありますね。

通信が失敗した時の処理

import 'dart:convert';

import 'packages:http/http.dart' as http;

void fetchSomethingRequest(BuildContext context) {
  final scaffold = Scaffold.of(context);
  const url = '...';
  http.get(url).then((response) {
    if (response.statusCode != 200) {
      throw Exception('Something occurred.');
    }
    final decodedBody = json.decode(response.body) as Map<String, dynamic>;
    setState(() {
      _item = SomeObject(
        id: decodedBody['id'],
        title: decodedBody['title'],
        // ...
      );
    });
    // ...
  }).catchError((error) {
    scaffold.showSnackBar(
      SnackBar(
        content: Text('Failed'),
      ),
    );
  });
}

解説

catchErrorを追加しました。
thenメソッドは新たなFutureを返却します。
それに対してcatchErrorを書く事でエラー時の処理を記述することが出来ます。

現状の問題を整理

さて、大体出来ましたがここで問題を整理してみましょう。

1. ネストが深くなっていて読みづらい
2. “fetchSomethingRequest”メソッドの呼び出し側が通信の内容によって処理を出し分けるなどが出来ない
3. 2に起因しているところではあるがUI系処理とロジック系処理が一つのメソッドに入っているので可読性が悪い

これらを解消していきましょう。

Futureを返してみよう

import 'dart:convert';

import 'packages:http/http.dart' as http;

Future<void> fetchSomethingRequest(BuildContext context) {
  final scaffold = Scaffold.of(context);
  const url = '...';
  return http.get(url).then((response) {
    if (response.statusCode != 200) {
      throw Exception('Something occurred.');
    }
    final decodedBody = json.decode(response.body) as Map<String, dynamic>;
    setState(() {
      _item = SomeObject(
        id: decodedBody['id'],
        title: decodedBody['title'],
        // ...
      );
    });
    // ...
  });
}

@Override 
Widget build(BuildContext context) {
  return FlatButton(
    child: ...,
    onPressed() {
      fetchSomethingRequest(context).catchError((error) {
        scaffold.showSnackBar(
          SnackBar(
           content: Text('Failed'),
          ),
        );
      });
    }
  );
}

解説

Futureを返す様にしました。
Futureインスタンスが持つthenメソッドやcatchErrorメソッドはFutureを返します。
なので、この値をメソッドの呼び出し側に返却しましょう。
こうすることにより、呼び出し側で処理の出し分けが出来る様になりました。
さらにUIの処理をUI側に移動させることが出来ました。

asyncの追加

import 'dart:convert';

import 'packages:http/http.dart' as http;

Future<void> fetchSomethingRequest(BuildContext context) async {
  final scaffold = Scaffold.of(context);
  const url = '...';
  http.get(url).then((response) {
    if (response.statusCode != 200) {
      throw Exception('Something occurred.');
    }
    final decodedBody = json.decode(response.body) as Map<String, dynamic>;
    setState(() {
      _item = SomeObject(
        id: decodedBody['id'],
        title: decodedBody['title'],
        // ...
      );
    });
    // ...
  });
}

// buildメソッド...

解説

asyncと言うキーワードが出てきました。

Asynchronous programming: futures, async, await
これを波括弧の前に記述すると、そのメソッドは全てFutureでラップされます。

今回の例では、何も返していないので本来であれば最初の例がそうであった様にvoid型です。
今asyncキーワードを付けた事によりreturn式を書かなくても”Future<void>を返しています。
言い換えるなら最後に “return Future.value();”を書いているのと等価と言う事です。

awaitの追加

import 'dart:convert';

import 'packages:http/http.dart' as http;

Future<void> fetchSomethingRequest(BuildContext context) async {
  final scaffold = Scaffold.of(context);
  const url = '...';
  final response = await http.get(url);
  if (response.statusCode != 200) {
    throw Exception('Something occurred.');
  }
  final decodedBody = json.decode(response.body) as Map<String, dynamic>;
  setState(() {
    _item = SomeObject(
      id: decodedBody['id'],
      title: decodedBody['title'],
      // ...
    );
  });
}

解説

ぱっと見、気が付きますか?
そうです、ネストが一つ減りました。

awaitキーワードは非同期処理が終わるまで待ってくれると言うものです。
すごく簡単に言うならFuture<T>をTに変換してくれると考えると楽かもしれません。

“http.get(url)”は”Future<Response>を返します。
このメソッドを呼ぶ際に”await”を付けるとこの処理が終わるまで待ってくれます。
なので、thenブロックが必要なくなり、さも同期処理の様に書くことができます。

通信処理などの非同期処理は必ず扱う処理の一つだと思うので、
ちゃんと理解して少しでも綺麗に書ける様になるのが大事ですね。誰かのお役に立てば。

Twitterフォローお願いします

「次回以降も記事を読んでみたい!」
「この辺分からなかったから質問したい!」

そんな時は、是非@daiki1003のフォローお願いします♪

コメント

タイトルとURLをコピーしました