質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

Q&A

解決済

2回答

1986閲覧

APIから取得する処理を繰り返し処理内で実行すると、途中で503エラーが発生することがある。

massanmesu

総合スコア36

Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

0グッド

0クリップ

投稿2021/10/15 06:28

前提・実現したいこと

カーリルというサイトのAPIから図書館のデータを取得する機能を実装しています。
システムID(図書管理システムのユニークID)と本が持つ固有のISBN番号をURLに入れることで、指定の本がその図書館で取り扱っているか、またその本の貸し出し状況を取得したいです。

以下リクエストとレスポンスの例
イメージ説明
レスポンスにcontinueという値があり、0はロード完了、1はロード未完了を表しています。
サイトに記載の通り、1の場合は最低2秒の間隔をあけて再度ロードし、全てのロードが完了することを推奨しております。(当たり前ですが)

私の場合、本を一冊、複数のシステムIDから検索したいので、システムIDを格納したリストからforで取り出し、繰り返し処理内で本を検索するコードを書きました。

最初に一通り検索、その後continueが1のデータがある限り、そのデータを再ロードし続けるというコードです。

dart

1// 本のISBN 2String isbn = '9784478025819'; 3// システムID 4List<String> systemIdList = []; 5// システムIDごとのレスポンスのリスト 6List<LibraryDataHasBook> bookDataResult = []; 7 8// 関数 9// ISNBを使って図書館を検索 10Future<dynamic> getLibraryUseISNB({String? systemId, String? session}) async { 11 Uri url; 12 if (session != null && systemId == null) { 13 url = Uri.parse( 14 'https://api.calil.jp/check?session=$session&format=json&callback=no'); 15 } else { 16 url = Uri.parse( 17 'https://api.calil.jp/check?appkey=$apiKey&isbn=$isbn&systemid=$systemId&format=json&callback=no'); 18 } 19 http.Response _response = await http.get(url); 20 if (_response.statusCode == 200) { 21 return jsonDecode(_response.body); 22 } else { 23 print('エラー発生。エラーコード:${_response.statusCode}'); 24 return null; 25 } 26} 27 28// 全てのレスポンスのロードが完了するまで再ロードする 29Future<dynamic> getCompletedLoadingSearchResult() async { 30 var results = []; 31 for (var systemId in systemIdList) { 32 // レスポンス取得 33 final response = await getLibraryUseISNB(systemId: systemId); 34 results.add(response); 35 } 36 37 // ロードが未完のデータがあればそこだけ再実行 38 while (results.where((result) => result['continue'] == 1).isNotEmpty) { 39 print('continue = 1 のため再取得開始'); 40 // 5秒待つ 41 await Future.delayed(const Duration(seconds: 5)); 42 // continue=1のデータのみ再度実行 43 results.asMap().forEach((index, result) async { 44 if (result['continue'] == 1) { 45 results[index] = await getLibraryUseISNB(session: result['session']); 46 } 47 }); 48 } 49 50 return results; 51} 52 53// システムIDを使って本を検索しモデル化してリストを更新 54Future<void> searchAllBookData() async { 55 List<LibraryDataHasBook> list = []; 56 // ロードを完了したレスポンスのリストを取得 57 final response = await getCompletedLoadingSearchResult(); 58 var index = 0; 59 // モデル化 60 for (var data in response) { 61 var libKey = systemIdList[index]; 62 list.add(LibraryDataHasBook( 63 systemId: libKey, 64 status: data['books'][isbn][libKey]['status'], 65 libkey: data['books'][isbn][libKey]['libkey'], 66 url: data['books'][isbn][libKey]['reserveurl'])); 67 index++; 68 } 69 bookDataResult = list; 70} 71 72 73// テスト 74void main() { 75 76 test('システムIDごとに本の検索', () async { 77 // システムIDのリスト 78 systemIdList = [ 79 'Kanagawa_Sagamihara', 80 'Univ_Azabu', 81 'Special_Jaxa', 82 'Univ_Aoyamagakuin', 83 'Univ_Kitasato', 84 'Univ_Obirin', 85 'Univ_Joshibi', 86 'Tokyo_Machida', 87 'Univ_Yamazaki', 88 'Tokyo_Hachioji', 89 'Univ_Tmu', 90 'Univ_Otsuma', 91 'Univ_Salesio', 92 'Tokyo_Tama', 93 'Univ_Sagami_Wu', 94 'Univ_Tamabi', 95 'Kanagawa_Atsugi', 96 'Kanagawa_Zama', 97 'Univ_Toyaku', 98 'Univ_Keisen', 99 'Univ_Chuo', 100 'Univ_Yamano', 101 'Tokyo_Hino', 102 'Kanagawa_Aikawa', 103 'Univ_Teu', 104 'Univ_Meisei', 105 'Univ_Zokei', 106 'Univ_Teikyo', 107 'Univ_Kokushikan', 108 'Kanagawa_Yamato', 109 'Univ_Kait', 110 'Univ_Nms', 111 'Kanagawa_Ebina', 112 'Univ_Tamagawa', 113 'Univ_Jissen' 114 ]; 115 116 // 本を検索 117 await searchAllBookData(); 118 // 中身を確認するコード 119 bookDataResult.asMap().forEach((int i, LibraryDataHasBook data) { 120 print('no.${i + 1}----------'); 121 print(""" 122 systemid: ${data.systemId} 123 status: ${data.status} 124 url: ${data.url} 125 libkey: ${data.libkey} 126 """); 127 }); 128 }); 129}

発生している問題・エラーメッセージ

ロードが完了するまで繰り返し実行しているのですが、何故か3回目で頻繁にエラーが発生します。
カーネルにエラーコード503についての補足が見つからなかったので調べましたが、一般的に503は『一時的なアクセス不可の状態』とのことでした。

/Users/user/flutter_dev/flutter/bin/flutter --no-color test --machine --start-paused --plain-name システムIDごとに本の検索 test/get_response.dart Testing started at 14:58 ... continue = 1 のため再取得開始 continue = 1 のため再取得開始 ←2回目までは実行できてる? エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 エラー発生。エラーコード:503 continue = 1 のため再取得開始 dart:core Object.noSuchMethod test/get_response.dart 134:17 getCompletedLoadingSearchResult.<fn> test/get_response.dart 133:29 getCompletedLoadingSearchResult.<fn> dart:_internal ListMapView.forEach test/get_response.dart 133:21 getCompletedLoadingSearchResult ===== asynchronous gap =========================== dart:async _completeOnAsyncError test/get_response.dart getCompletedLoadingSearchResult.<fn> test/get_response.dart 133:29 getCompletedLoadingSearchResult.<fn> dart:_internal ListMapView.forEach test/get_response.dart 133:21 getCompletedLoadingSearchResult ===== asynchronous gap =========================== dart:async _asyncThenWrapperHelper test/get_response.dart 146:26 searchAllBookData test/get_response.dart 344:11 main.<fn> test/get_response.dart 305:25 main.<fn> NoSuchMethodError: The method '[]' was called on null. Receiver: null Tried calling: []("continue") // 以下、似たようなエラー文のため省略

試したこと

・時間をおいて再実行 → 成功するケースもあれば失敗するケースもあり再現性がない
・別の本を検索した後で再実行 → 同上
・待機時間を5秒〜15秒の間隔で試したが変化なし
・たまに一発で成功するケースもある

以上よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

自己解決

beadv様のご回答を参考にしてforEachをしている処理のasyncを消したところ、『asyncがありません』というエラーが発生しました。

検索してみたらFuture.forEach()という非同期処理用のforEachメソッドがありました。

dart

1int index = 0; 2await Future.forEach(results, (result) async { 3 result = result as Map<String ,dynamic>; 4 if (result['continue'] == 1) { 5 results[index] = await getLibraryUseISNB(session: result['session']); 6 } 7 index++; 8 });

ただ、このメソッドはインデックスの取得ができないことと、結局asyncをつけないとエラーになるので、以下のコードに書き換えて実行したとこ上手く機能しました。

dart

1int index = 0; 2for (var result in results) { 3 if (result['continue'] == 1) { 4 results[index] = await getLibraryUseISNB(session: result['session']); 5 } 6 index++; 7}

一つの処理の中で繰り返し処理があり、その中で非同期処理を複数回実行させたい場合は上記のコード、そうじゃない場合はFuture.forEach()を使う、ということに落ち着きました。

なぜforEach()だとasyncが必須なのにfor文だと要らないのかは不明ですが、解決したのでよかったです。

投稿2021/10/15 22:58

massanmesu

総合スコア36

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

こんにちは。

getCompletedLoadingSearchResult の中の
results.asMap().forEach((index, result) async {
の記述ですが、ここにasync があることで、このfor文は中で処理されている
results[index] = await getLibraryUseISNB(session: result['session']);
の処理を待たずしてループを抜けているということはないでしょうか?
(async の {} の部分が別スレッドになって非同期処理している)

その結果、while のループの2回目に突入し、5秒待った時点で、まだ前回の
getLibraryUseISNB(session: result['session']);が終わっていない
あるいは終わった直後のものがあり、ポーリングの間隔が短いセッションが発生して
エラーになっているのではないかなぁと思うのですが、
このasyncをとるか、このforeach 部分を外に出してFutureを宣言したサブルーチンに
変更したら、改善しないでしょうか?

投稿2021/10/15 08:40

beadv

総合スコア144

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

massanmesu

2021/10/15 09:35

beadv様 ご教授ありがとうございます。 「awaitをつけるときは必ずその処理をしている{}にasyncをつける」という覚え方をしていたので、不必要にスレッドをつくってしまっているのかな、とご回答を読んで思いました。 もし関数内で非同期処理をする場合、別スレッドで処理したい場合以外は関数の{}にひとつasyncを付けるだけで良い、ということなのでしょうか。 今日はもうできない状態なので明日の朝に確認してみます。
beadv

2021/10/15 23:45

>非同期処理をする場合、別スレッドで処理したい場合以外 非同期処理をする=別スレッドで処理する ですので、きっと「非同期処理をする場合」ではなくて 「非同期処理を待つ場合」という意味合いですね。 処理がawaitに当たるとそのスレッドの処理が止まってしまうので、メインスレッドが止まって 無応答にならないように、awaitはasyncの処理内でなければ書けないと思いますが、 関数レベルでasyncなら大丈夫だったんじゃないかと思います。(間違ってたらすみません) ちょっと気になるのがforeachがメソッドなので、この部分が内部関数として 1塊と認識されていないか、ということなんですが(この場合asyncが外せないと思います) 実際問題として、ここのforeach部分が非同期処理になると、システムIDの数だけ 先方のサーバにウエイトなしでリクエストを投げることになるので、あまりよろしくないと 思います。なので、例えばresults.asMap() をいったん別の変数で受けて、 その変数のリストの長さの分だけfor文で回す、などで、リクエストがパラレルで動かない 方向で検討した方がよいのではないかと思います。
massanmesu

2021/10/16 00:32

関数の中でforEachを使う場合の話ですよね? 関数のスレッドとは別のスレッドで処理されてしまうので大元の関数の処理を無視してリクエストを送ってしまうという認識でしょうか(いまいち理解が追いついていません)。 以下のようなコードにしろ、ということでしょうか? int length = results.length; for (int i = 0; i >= length; i++) { // 非同期処理 }
beadv

2021/10/16 02:46

>関数の中でforEachを使う場合の話ですよね? ちょっとニュアンスが違うかもしれません。 例えば元のコードですが while (results.where((result) => result['continue'] == 1).isNotEmpty) { print('continue = 1 のため再取得開始'); // 5秒待つ await Future.delayed(const Duration(seconds: 5)); この部分、await を使用しているのに、直前の whileに続く{ にはasyncがないですよね? if やwhile やfor は単なる制御文なので、この場合、このawaitがどの関数の中にあるか? といえばgetCompletedLoadingSearchResultの中、ということになり、この関数がasyncなので 大丈夫なんだと思います。 それに対してresults.asMap().forEach((index, result) async { という文のforEachは results.asMap()のメソッドで、引数として、元のオブジェクトの集合から取り出した1つの index,resultの組を受け取る変数と、各々のオブジェクトが実際に実行したい処理を 関数として受け取っているのです。この場合async { 以降対応する} までが名前のない 関数の定義ですので、この処理の中にawaitを記述するならこの関数のボディは asyncで ある必要があるという解釈です。 ざっくりしたイメージですが、 〇〇.foreach(xxx  {  処理1  処理2  処理3 }) となっているときに、〇〇がA,Bの2つの集合なら、処理の順番は  A.処理1  A.処理2  A.処理3  B.処理1  B.処理2  B.処理3 となると思います。asyncがないので、Aの処理の塊が終わってBの処理、という流れですね。 ここで、処理2が非同期処理だとして 〇〇.foreach(xxx async {  処理1  await 処理2  処理3 }) だと、Aの処理2の実行に入ったところでいったんこの処理はストップして、  A.処理1  A.処理2 で処理中断したところで、B.処理1  B.処理2 で処理中断  A,処理2終わり  B.処理2終わり  A.処理3  B.処理3    のように流れが変わります。(B.処理2 で処理中断以降はこのパターンなるとは限りません)  awaitにぶつかったところでAに対する処理とB.に対する処理がそれぞれ別々に  行われてしまうイメージです。    前回のコメントの例えば、でご提案したのは、foreachメソッドではなくfor文にすることで、awaitを使っても  Aの処理が終わってBの処理という流れにした方がよいのではないかということですので  ご自身で解決されたresultsのfor文と意味合い的には一緒です。  (元のコーディングのイメージと対比して考えたのでインデックスを使うような流れを   例として挙げました)    長くなってしまいすみません。イメージわきますでしょうか?
massanmesu

2021/10/16 03:45

ご丁寧に解説していただき感謝です!! results.asMap().forEach((index, result) async { await } の処理だと一つ目のresultのawaitが実行したら二つ目のresultの処理が始まってしまい、awaitのタイミングでまた次のresultが処理されるので、awaitの処理がAPI通信の場合だと「サーバにウエイトなしでリクエストを投げることになる」という認識でよろしいでしょうか?
beadv

2021/10/16 03:54

はい。そのような動きをすると思います。
massanmesu

2021/10/16 04:19

勉強になりました。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問