for文内でのkintone.proxyによるXMLデータの取得について

お世話になっております。

 

表題の件で、期待する値を取得することができず困っておりますので、ご教示ください。

 

商品APIの仕様

  • 商品コードを渡すと商品の詳細をXML形式で返す
  • メソッド:GET

プログラムの仕様

  • 受注アプリと受注明細アプリがあり、受注番号で関連付けられている
  • 発注アプリと発注明細アプリがあり、発注番号で関連付けられている
  • 受注アプリの"手配する"ボタンをクリックすると、まず受注番号で関連付けられている受注明細アプリのデータを取得する(kintone.apiのGET)
  • 複数の受注明細アプリデータの商品コードを1件ずつ商品APIへ渡し、商品名、仕入先名、仕入単価などを取得する。(kintone.proxy)
  • 取得した商品の詳細データを1件ずつ発注明細アプリへ登録する(kintone.apiのPOST)

困っている事

  • kintone.proxyをfor文の中で実行しますが、すべての商品情報を取得する前に、発注明細アプリへの登録処理が実行されてしまう事

 

ジョイゾー様のブログ(https://www.joyzo.co.jp/blog/1155) なども参考にしてkintone.promise等も勉強しましたが、思うように商品情報をkintone.proxyで取得し、発注明細アプリへ登録することができません。

 

恐れ入りますが、助けて頂ければと思います。

i9yosimo さん

 

お気付きの通りPromiseの処理がマッチしそうです。「非同期処理をfor文で・・・」みたいなトピックが過去に幾つかありましたが、Promiseを利用すればむしろfor文での記載ではなくなります。シーケンシャルに非同期処理を流していくのに、近いのはこちらのトピックが参考になりそうですので、チェックされてみてはいかがでしょうか。

Ryu Yamashita 様

 

早速のご回答、ありがとうございます。

ご紹介いただいた記事はすべては理解できておりませんが、取り急ぎお礼をと思い、コメントを投稿させていただきました。

どうもありがとうございます。

分からない点があれば、またこのスレッドで質問させていただきます。

Ryu Yamashita 様

 

大変お世話になっております。

ご教示いただいたトピックをもとにサンプルプログラムをコーディングをして、商品APIの商品名を取得するところまでできましたが、最初の1件目の商品名を取得したのみで、次の商品名を取得することができません。

コードは下記の通りです。

kintone.events.on("app.record.create.submit", function(event) {
var record = event.record;

var itemCodeList = [];
itemCodeList.push(record["連携項目1"]["value"]);
itemCodeList.push(record["連携項目2"]["value"]);

var promise = new kintone.Promise(function(resolve, reject) {

var proxy_promises = itemCodeList.map(function(itemCode) {
return function() {
return new Promise(function(proxy_resolve) {
var url = "https://○○○○.○○.○○:8080/apis/item?id=" + itemCode;
kintone.proxy(url, "GET", {}, {}).then(function(resp) {
var parser = new DOMParser();
var xml = parser.parseFromString(resp[0], "application/xml");

var itemNameListFromXml = xml.getElementsByTagName("DisplayName");
proxy_resolve(itemNameListFromXml[0].childNodes[0].nodeValue);
});
});
};
});
var seq = proxy_promises.reduce(function(a, b) {
return a.then(b);
}, Promise.resolve(null)).then(function(itemName) {
alert(itemName);
});

 

最後から3行目の Promise.resolve(null)).then(function(itemName)) { でitemNameに1件目の商品名を取得できたのですが、2件目の商品名が取得できません。

どこを、どのように修正すれば2件とも商品名を取得できるでしょうか?

お手数をおかけしますが、ご教示ください。

i9yosimoさん
cstapの瀧ヶ平です。

横から失礼します。
山下さんが上げたトピックの中で私が回答しているコードは修正部分が多く読みにくいと思い、解説させていただきます。

まず、app.record.create.submitイベントでは、kintone.Promiseオブジェクトがreturn された場合に、そのkintone.Promiseオブジェクトで行われている非同期処理がすべて終了するまで待ってからレコードの登録処理を行います。
ですので、kintone.events.onのコールバック関数内ではkintone.Promiseオブジェクトをreturnする必要があります。

また、Promiseの直列処理ですが、Promiseの.thenチェーンの処理では

new Promise(function(resolve) {
resolve(value);
}).then(function(val) {
// val は最初にresolveしたvalueの値
return val * 2;
}).then(function(val_2) {
// val_2 は 前の関数でreturn した値 e.g. val * 2 === value * 2;
});

というように前のthen内の関数でreturnした値もしくは前のPromiseでresolveに渡した引数がが次のthen内の関数の引数として与えられます。

i9yosimoさんのコードではitemNameListFormXml[0].childNodes[0].nodeValueの値をresolveしていますが、resolveされた値を次のthenに渡された関数(つまり、proxy_promises配列での次の関数)内部で使われていないため、そちらを修正する必要があります。
こちらの修正が難しければ、Promise関数内でresolveする前に別の配列にpushしておき、一番最後の行のthen関数内部でその配列の中身を順にalertしていく方法でも良いかと思います。

Promiseの直列処理については、Promiseの性質やmap関数やreduce関数などの配列操作関数など煩雑な部分が多いと思いますので、各所で紹介していますが、JavaScript Promiseの本や、こちらの記事などを参考にすることをおすすめします。

cstap 瀧ヶ平 様

 

ご回答ありがとうございます。

実は別のTipsでJavaScript Promiseの本を発見し、読み進めているところです。

2.7. Promiseと配列2.8. Promise.allがわたしが探している情報にピッタリなのではないかと思います。

まだすべてを読み進めて、理解しているわけではありませんが、取り急ぎご回答のお礼とさせていただきます。

 

また分からない点があれば山下様、瀧ヶ平様に質問させていただきます。

どうぞよろしくお願いいたします。

瀧ヶ平さん、フォローありがとうございます。

 

こちらのkintone.Promiseのいち考察(書いたのは私ですが)もチェック頂くと良いかもしれません。また、ちょっと込み入ったPromiseの使い方でも英語圏まで調べる範囲を広げると色々記事が出てきますので、必要に応じて確認するのが良いのかなぁと思っています。

山下さん、追加の情報をありがとうございます。またお礼が遅くなりましたことをお詫びいたします。

JavaScript Promiseの本 の2.8. Promise.all を読んで、kintone.proxyにてpromise処理を配列に格納して複数の受注データの情報を商品APIから取得するところまでは何とかこぎつけました。

しかしそのresolveでkintone.apiの"POST", "GET"をしようとすると予期せぬエラーが発生し、rejectに処理が流れてしまいます。そのプログラムは下記の通りです。

(function() {
"use strict";

//レコード登録画面の保存時
kintone.events.on("app.record.create.submit", function(event) {
var record = event.record;

var itemCodeList = [];
itemCodeList.push(record["連携項目1"]["value"]);
itemCodeList.push(record["連携項目2"]["value"]);

function getItemNameFromItemCode(itemCode) {
return new kintone.Promise(function(proxy_resolve) {
var url = "https://○○○○.○○.○○:8080/apis/item?id=" + itemCode;
kintone.proxy(url, "GET", {}, {}).then(function(resp) {
var parser = new DOMParser();
var xml = parser.parseFromString(resp[0], "application/xml");
var itemNameListFromXml = xml.getElementsByTagName("DisplayName");
proxy_resolve(itemNameListFromXml[0].childNodes[0].nodeValue);
});
});
}

var functionList = [];
functionList.push(getItemNameFromItemCode(itemCodeList[0]));
functionList.push(getItemNameFromItemCode(itemCodeList[1]));

var itemNameList = [];
Promise.all(functionList).then(function(itemName) {
for (var i = 0; i < itemName.length; i++) {
itemNameList.push(itemName[i]);
}
alert(itemNameList[0]);
alert(itemNameList[1]);

var promise = new kintone.Promise(function(resolve, reject) {
var copyItem = {
"app": 2,
"record": {
"連携項目1": {"value": itemNameList[0]},
"連携項目2": {"value": itemNameList[1]}
}
};

kintone.api("/k/v1/record", "POST", copyItem,
function(post_resp) { //callback
alert("POST成功");
var query = {
"app": 2,
"id": post_resp["id"]
};
kintone.api("/k/v1/record", "GET", query,
function(get_resp) { //callback
alert("GET成功");
resolve(get_resp); //正常ならthenへ
}, function(get_error) { //errorback
alert("GET失敗");
reject(get_error); //エラーはcatchへ
}
);
}, function(post_error) { //errorback
alert("POST失敗");
reject(post_error); //エラーはcatchへ
}
);

}).then(function(resp) {
alert("Promise成功");
record["連携先アプリのレコード番号"]["value"] = resp["record"]["レコード番号"]["value"];
alert(resp["record"]["レコード番号"]["value"]);
return event;
}).catch(function(error) {
alert("Promise失敗");
event.error = "連携先アプリに登録できませんでした。";
return event;
});

return promise;

}).catch(function(error) {
event.error = "商品名を取得できませんでした。";
return event;
});

});
})();

お忙しいところ、大変恐縮ですが、知恵をお借りできれば幸いです。

竹下様、瀧ヶ平様、どうぞよろしくお願いいたします。

i9yosimoさん

GET,POSTの処理でエラーが発生するとのことですが、開発者ツールなどで通信のエラーを確認などしましたでしょうか?(参考)

どのような値を送信したときにエラーが発生しているか、どのようなエラーメッセージが帰ってきているかなど教えていただけると問題の切り分けが楽になります。

 

瀧ヶ平様

 

連絡ありがとうございます。

開発者ツールは当然使っておりますが、Networkについてはあまり意識したことがありませんでした。

ご紹介いただいた記事を勉強し、結果を報告させていただきます。

取り急ぎ、お礼とさせていただきます。

瀧ヶ平様

 

エラーが発生した際の、開発ツール –> Networkの画像は張り付けてみます。

POSTが成功し、登録したデータをGETしようとした際に"Provisional headers are shown"が発生しているようです。

しかしながら、ソースコードを見ていただければ分かる通り、POST, GETともにkintone.apiを利用しているため、クロスドメインしようとしているわけではありません。

これで何かわかるでしょうか?

恐れ入りますが、ご教示ください。

Networkタブで見るべきは、HeadersとPreviewですね。エラーはPreviewにメッセージがのってくるので、原因はそちらから掴めることの方が多いです。

山下様

 

ご指摘頂きまして、ありがとうございます。

開発ツール –> NetwordのPreviewもチェックしたのですが、目立った情報がなかったので、画像をアップしませんでした。

この機会にアップさせていただきます。

ご覧頂いて分かる通り、Previewタブでは"Failed to load response data"と表示され、レスポンスの取得に失敗しているとのメッセージが表示されています。

これはGETだけではなく、直前のPOSTでも同様です。

通常、GETはリクエストした内容を、POSTは登録したレコードのレコードIDとrevisionが表示されると期待していたのですが、それがありません。

報告とさせていただきます。

よく見ると Promise.all の前にreturn が要りそうに感じました。瀧ヶ平さんが途中に書かれていたように、submitの時にはkintone.Promiseオブジェクト(のチェーン)をreturnすることで、保存処理がPromiseチェーンの処理を待ってくれます。ですので、REST APIの処理が終わる前に保存が終わっている可能性が高そうに思いました。

 

ちなみに、成功しているPOSTは表示されていそうなのと、ブレークポイントはって原因追ったりはされていますか?(意図した順序で流れているかの確認)

 

また、Promise.all はnative Promiseになりますが、kintone.Promiseも.allを持っていますので、kintone.Promise.all() を使われると良いかと思います。submitのreturnの件と合わせて、こちらも確認頂くと良いかもしれません。

 

Promiseも慣れて感覚掴んでいくしかないと思うので、自ら試行錯誤されてみてください。また、英語圏も含めて色々パターンを見るのも良いともいます。

山下様、瀧ヶ平様

 

大変お世話になっております。

試行錯誤の結果、なんとか私の希望する処理が達成できそうです。

まだまだ勉強不足で、kintone.promiseの処理の仕方について明確に理解すべき点が残っていますが、山下様のおっしゃられる通りでプログラミングを通して慣れていくしかないと思いますので、これからも頑張っていきたいと思います。

お二人には心より感謝申し上げます。

どうもありがとうございました。