繰り返し文の中で複数のPromiseを行う場合のコーディング手法について

いつもお世話になっております。
標記の件ですが、以下の流れで処理を行うようにjavascriptで実装しているのですが、結果として1行しか処理できていません。

①Aアプリの一覧画面にそれぞれのレコードのサブテーブルの内容を更新するボタンを表示する
②①のボタンを押下すると一覧画面に表示されている全てのレコードをREST APIにてGETする
③一覧画面に表示されている全てのレコードに対して以下④~⑤の処理を行う
④Aアプリのレコード番号に合致するBアプリのレコードを取得する(BアプリにはAアプリのレコード番号は登録済)
⑤Bアプリより取得したレコードを加工し、AアプリのレコードのサブテーブルにPUTする

上記の流れのアプリを作成するにあたり、以下の投稿を参考にしております。
https://developer.cybozu.io/hc/ja/community/posts/204669873

私見では上記の投稿にもありますように、for文でのPromiseの呼び出しにて最初の1行しか実行されないことに起因しており、対処として.reduce関数の利用、もしくは.then()でメソッドをチェーンしていく必要があると読み解いております。
現在.reduce()の使い方がよくわからないこともあり、.thenでメソッドをチェーンする方法で実装しておりますがメソッドをチェーンする・しないに関わらず一覧画面に表示されているデータ1つしか処理されていません。
具体的なプログラムとしては、以下のように実装しています。

/* プログラム(ここから) */

(function() {

“use strict”;

// Aアプリ、Bアプリ共通で使用するデータを保持する
var DataDao = {};

kintone.events.on(‘app.record.index.show’, function(event) {

//ボタン作成
/////////////////////////////////////////////////////////////////
// ボタン増殖防止
if (document.getElementById(‘Menu_Button’) !== null) {
return;
}

////////////ボタン定義(ここから)////////////////
var MenuButton = document.createElement(‘button’);
//ボタンのID指定
MenuButton.id = ‘Menu_Button’;
//ボタン名表示
MenuButton.innerHTML = ‘一括更新’;
//ボタンサイズ
MenuButton.style.width = ‘200px’;
//左側のスペース
MenuButton.style.marginLeft = ‘250px’;

////////////ボタン定義(ここまで)////////////////

////////////メインルーチン(ここから)////////////////

DataDao.gettingIchiran().then(function (resp){

var resultIchiranRecord = resp[‘records’]; //Aアプリ一覧画面に表示されているレコードを取得

for(var row = 0; row < resultIchiranRecord.length ; row++){ //Aアプリのレコード1つ1つに対して以下の処理を行う

DataDao.gettingB(row, resultIchiranRecord).then(function (past_resp){ //Bアプリからデータ取得

var resultPastRecord = past_resp[‘records’]; //一覧画面に表示されているレコードを取得

}, function(err){ // エラー処理
console.log(err);
}).then(function (past_resp){

DataDao.updatingIchiran(row, resultIchiranRecord, past_resp.resultPastRecord); //Aアプリの各レコードのサブテーブル更新

});
} //for文の終わり
}, function(err){ // エラー開始
console.log(err);
}); // エラー処理の終わり

}; //onclickイベント終了

kintone.app.getHeaderMenuSpaceElement().appendChild(MenuButton); //ボタン表示

return event;

})// kintone.events.onの終わり

DataDao.gettingIchiran = function() {

var index_param = {
“app” : “AアプリのアプリID” //AアプリのアプリID
};

return kintone.api(kintone.api.url(‘/k/v1/records’, true), ‘GET’, index_param);

};// DataDao.gettingIchiranの終わり

houjiDao.gettingB = function(row_inf,resultIchiranRecord_inf) {

// Bアプリから該当するレコード番号のデータをGET

var record_No = resultIchiranRecord_inf[row_inf][“$id”][“value”];

var queryCondition = " 会員コード = " + record_No ;

var past_param = {
“app” : “BアプリのアプリID”,
“query” : queryCondition , // 検索条件
}; //end past_param

return kintone.api(kintone.api.url(‘/k/v1/records’, true), ‘GET’, past_param);

}; //houjiDao.gettingBの終わり

DataDao.updatingIchiran = function(row_inf, resultIchiranRecord_inf, resultPastRecord_Inf) {

/// サブテーブルに格納するデータの作成を行い、tableRecordsに格納する///

/* Aアプリのレコードを更新(PUT) */
kintone.api(kintone.api.url(‘/k/v1/record’, true), ‘PUT’,
{
“app”:appId, “BアプリのアプリID”
“id”:record_No, //更新対象のレコード
“record” :tableRecords // 更新するレコードのサブテーブル

})

}; // DataDao.updatingIchiranの終わり

})(); // functionの終わり

/* プログラム(ここまで) */

先ほど少し述べましたが1件だけは正しく処理できていますので、それぞれのfunctionの処理自体は問題ないと考えています。
こうすれば実現可能など、ご意見を頂戴できないでしょうか(そもそもで現在のロジックがおかしいなどのご意見でもかまいません)
どうぞよろしくお願いいたします。

持田さん

DataDao.gettingB が非同期処理ですので「for」は、DataDao.gettingB の完了を待たずに進んでいきます。

その結果、 DataDao.gettingB が同時に呼ばれていますが、ブラウザーの制限で API を同時に呼び出せるのは6個程度です。

それでも順次API は実行されますが、最初に API の応答が戻ってくる頃には for ループが進んでおり、row 等の変数は、期待している値ではなくなっています。

対応策ですが、「for」のループ処理の代わりに処理を関数化して再帰呼び出ししましょう。

下記が参考になると思います。

Promiseを利用したモダンなアプリの全レコード取得の書き方

rex0220様

いつも迅速な回答をいただきありがとうございます。

アドバイスいただいたページを参考に実装を進めてみます。

取り急ぎお礼まで。

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

rex0220様

いつもお世話になっております。

先ほど頂戴したコメントについて小職の中で少々混乱しており、基本的なことで大変恐縮ですがご教示ください。

>DataDao.gettingB が非同期処理ですので「for」は、DataDao.gettingB の完了を待たずに進んでいきます。

DataDao.gettingB自体は.thenをつけて同期処理を行うようにしているつもりなのですが、小職の作成したDataDao.gettingBでは同期処理の作りになっていない認識でよろしいでしょうか。

お手数をお掛けいたしますが、どうぞよろしくお願いいたします。

 

確かに gettingB の処理が完了したら、updatingIchiran が呼ばれる構造になっています。

1 回だけ実行される処理なら問題ありませんが、for ループの中で複数回呼ぶ場合は、持田さんの期待通りの動きになりません。

いまのところ、解決策は Promise 対応した関数の再帰処理がよさそうです。

 

rex0220様

いつもお世話になっております。

早々のアドバイスありがとうございました。

いただいたアドバイスを活用させていただき実装を進めてみます。

 

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

 

rex0220様

いつもお世話になっております。
現在も実装を進めておりますが今更ながらで申し訳ございませんが、1点確認させてください。

>対応策ですが、「for」のループ処理の代わりに処理を関数化して再帰呼び出ししましょう。

いただいたアドバイスの趣旨としては、関数化した処理の引数に別の関数を指定するイメージでしょうか。
(本スレッドのプログラムを使っての例で申しますと、gettingIchiranの引数にgettingBを指定するということでしょうか?
的はずれなことであれば申し訳ございません。)

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

Promiseを利用したモダンなアプリの全レコード取得の書き方 にある「fetchRecords」が参考になると思います。

「fetchRecords」は、処理を続ける場合「fetchRecords」自身を呼び出しています。

このような処理を再起呼び出しといいます。

For ループでレコード数分繰り返す代わりに、再起呼び出しでレコード数分繰り返します。

このような構造であれば、非同期のAPI 処理を順番に処理することができます。

 

rex0220様

いつもお世話になっております。
アドバイスいつもありがとうございます。
fetchRecords関数については確認させていただきました。
度々恐縮ですが、1点ご教示いただけたらと思いますが、fetchRecordsは再帰的に呼び出すことにより特定のアプリ中のデータを一度に取得する関数ですが、当アプリのように、Aアプリから取得した結果から条件を指定してBアプリのレコードを取得したい場合には、AアプリのレコードおよびBアプリのレコードを全てfetchRecords関数を使って取得後
にAアプリの取得結果とBアプリの取得結果を特定のレコードの条件(レコード番号が一致する)をもとに1件ずつ突き合わせるしか方法しか思いつきませんが、他に方法はございますでしょうか。
(最初に投稿した「④Aアプリのレコード番号に合致するBアプリのレコードを取得する(BアプリにはAアプリのレコード番号は登録済)」の実装で悩んでいます。
度々で恐縮ですが、どうぞよろしくお願いいたします。

fetchRecords関数は、API を一つしか使っていませんが、関数の中で GET API 後に、PUT API を呼んだ後に、再帰処理で自分自身を呼び出すことも可能です。
実装方法は、API の処理件数なども考慮して決めればいいと思います。

rex0220様

早速のアドバイスありがとうございます。

いただいたアドバイスをもとに、頑張って実装を進めてみます。

 

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

rex0220様

いつもお世話になっております。

時間がかかりましたが繰り返し部分について再帰処理により、求めている動作の実装ができました。

多々ご教示いただきありがとうございました。

今後ともよろしくお願いいたします

 

このトピックはベストアンサーに選ばれた返信から 3 日が経過したので自動的にクローズされました。新たに返信することはできません。