for文内でのpromiseの呼び出し方について

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

 

掲題の件で、教えて頂きたいのでご教授下さい。

 

https://www.joyzo.co.jp/blog/1155 

上記の記事等を参考にさせて頂き、実装しているのですが

そうなった場合にfor文内での記述をするとreturnされてしまうので、最初の1行しか処理されません。(当たり前なのはわかっていますが・・・)

 

 

鈴木佑介さん
cstapの瀧ヶ平です。

Promiseでは.then()でメソッドをチェーンすることによって次の.then(fn)の引数のコールバック関数に前のコールバック関数でreturnした値を渡すことができます。
どういった処理をするのかにもよりますが、ループする配列などから .map()関数を用いてPromiseを返す関数の配列を作り、.reduce関数で順番にthenでつなぐというような方法で非同期処理を順番に行うことができます。

var promises = arr.map(function(item){
return function(arg){
return new kintone.Promise(function(resolve,reject){
// ここに主な処理を書く
// ここでresolveした値が次のthen関数のコールバックに渡される
// kintone Proimiseオブジェクトをresolveした場合はそのthen内でreturnされた値が次のthen関数のコールバックの引数に渡される
});
}
});
promises.reduce(function(pre, cur){return pre.then(cur)},kintone.Promise.resolve());

具体的には上のようなコードで実装できます。

以上参考になりますでしょうか

cstap

瀧ケ平様

 

ご教授ありがとうございます。

やりたい事は下記の通りです。

①Aアプリの一覧画面にボタンを表出する(特に問題なし)

②ボタンを押下したら、別アプリ(Bアプリ)にレコードが存在していないかの重複チェックを行う

③重複あり→Bアプリ、Cアプリを更新。 重複なし→Bアプリ、Cアプリに新規登録

 

という内容になります。

 

イマイチ、基本的な部分がわかっていない所もあるのですが、

for文でloopさせつつ、というよりは配列ごとpromiseオブジェクトをreturnするfunctionに引数として渡す。という認識でしょうか?

return new kintone.Promise(function(resolve,reject){
// ここに主な処理を書く
// ここでresolveした値が次のthen関数のコールバックに渡される
// kintone Proimiseオブジェクトをresolveした場合はそのthen内でreturnされた値が次のthen関数のコールバックの引数に渡される
});

上記の内容は理解できます。

promises.reduce(function(pre, cur){return pre.then(cur)},kintone.Promise.resolve());

また、上記の内容がよくわかっておらず。。。

 

for文内で現在は下記のように実装しています。。。

 

for (var i = 0; i < target_records.length; i++) {
var target_record = target_records[i];
// レコードが存在するか確認
params = {app: TABLE_APP_ID, query: ‘HOGE = "’ + target_record[‘HOGE’].value + ‘" and KINTONE_ID != “”’};
kintone.api(kintone.api.url(‘/k/v1/records’, true), ‘GET’, params).then(function(resp) {
// レコードが無い場合は、内部IDを発番
if (resp.records.length === 0) {
params = {app: INFO_APP_ID, query: ‘order by KINTONE_ID desc limit 1’};
kintone.api(kintone.api.url(‘/k/v1/records’, true), ‘GET’, params).then(function(resp) {
if (resp.records.length === 0) {
// 内部IDがない事は想定しない

//rejectしたい

} else {

// 内部IDで、そのほかの情報の更新を行う
// kintone.apiを行う?
}
});
} else {
// ある場合は、内部Idを取得し、BアプリCアプリの登録情報を更新する
}
}, function(resp) {
event.error = ‘レコードの取得に失敗しました’;
});
}

 

コードも醜くなってきてしまっており、大変恐縮ですが

ご教授頂ければ幸いです。

鈴木佑介さん

そうですね、for文でループするのではなく、関数配列を作り順番にthenのメソッドチェーンでつないでいくという認識で正しいです。
reduceの処理に関してはこちらを見ればわかるのですが、配列の要素すべてに同じ関数を実行する関数です。コールバック関数の第一引数に前の関数でreturnされた値を、第二引数に現在の配列の値をとります。関数自体の第二引数は最初の実行時に第一引数に渡す値の指定になります。

ただ、おっしゃっている処理であれば、先ほどのPromiseによる順列処理は必要ないです。
また、ボタン押下でレコードを操作するのであればkintone.events.onの処理内でPromiseを返すのではなく普通にeventオブジェクトをreturnし、ボタンのonclickイベント内のみで処理を完結させるべきです。

そのため、以下のような形で実装すると良いかと思います。

button.addEventListener("click",function(){
for (var i = 0; i < target_records.length; i++) {
var target_record = target_records[i];
// レコードが存在するか確認
params = {
app: TABLE_APP_ID,
query: 'HOGE = "' + target_record['HOGE'].value + '" and KINTONE_ID != ""'
};
kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params).then(function(resp) {
// レコードが無い場合は、内部IDを発番
if (resp.records.length === 0) {
params = {app: INFO_APP_ID, query: 'order by KINTONE_ID desc limit 1'};
return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params);
} else {
resp.existsB = true;
return resp;
}
}, function(resp) {
alert('レコードの取得に失敗しました');
}).then(function(resp) {
if(!resp.existsB) {
if (resp.records.length === 0) {
// Bアプリにレコードがなく、Cアプリにもレコードがない場合の処理
} else {
// Bアプリにレコードがなく、Cアプリにはレコードがある場合の処理
}
} else {
// Bアプリにレコードがある場合の処理
}
});
}
});

 

cstap

瀧ケ平様

 

ご確認ありがとうございます。

>ただ、おっしゃっている処理であれば、先ほどのPromiseによる順列処理は必要ないです。
>また、ボタン押下でレコードを操作するのであればkintone.events.onの処理内でPromiseを返すのではなく普通にeventオブジェクトをreturnし、ボタンのonclickイベント内のみで処理を完結させるべきです。

おぉ、、そうなのですね。

という事は一覧表示イベントではボタン表出のみの処理を行い、

それとは別にクリックイベントの処理(瀧ケ平様が示してくれたコード)を記述するのが良いと理解しました。

 

示して頂いたコードの中で質問というか、認識があってるか確認頂きたいのですが、

kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params).then(function(resp) {
// レコードが無い場合は、内部IDを発番
if (resp.records.length === 0) {
params = {app: INFO_APP_ID, query: 'order by KINTONE_ID desc limit 1'};
return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params);
} else {
resp.existsB = true;
return resp;
}
}, function(resp) {
alert('レコードの取得に失敗しました');
}).then(function(resp) {

kintone.apiを行った結果responseが返ってきて、そのresponseに対してexistsBを設定しているものだと認識しております。

またその設定したresponseを2度目のthen(function(resp)){ の引数にすることで利用出来ているという認識でよかったでしょうか?

また、質問なのですが、この記述の場合にcatchの処理というのもかけるのでしょうか?

 

重ね重ねのご質問で大変恐縮ですが、レコードの登録や更新処理をbulkrequest.jsonを用いて行いたいと思っているのですが、それも記述の仕方によっては可能でしょうか?

何度もお手数お掛けし、申し訳ありませんがご確認頂ければ幸いです。

宜しくお願い致します。

鈴木佑介さん

>kintone.apiを行った結果responseが返ってきて、そのresponseに対してexistsBを設定しているものだと認識しております。

>またその設定したresponseを2度目のthen(function(resp)){ の引数にすることで利用出来ているという認識でよかったでしょうか?

>また、質問なのですが、この記述の場合にcatchの処理というのもかけるのでしょうか?

その認識で大丈夫です。
catchの処理ですが、そのまま書いた場合はおそらくCアプリからの取得時にエラーが発生したときのハンドリング処理になるかと思います。
そうではなく、能動的にrejectするような形でcatch処理を書きたい場合は最初のkintone.apiの処理の部分を

 kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params).then(function(resp) {
return new kintone.Promise(function(resolve,reject){
// レコードが無い場合は、内部IDを発番
if (resp.records.length === 0) {
params = {app: INFO_APP_ID, query: 'order by KINTONE_ID desc limit 1'};
resolve(kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params));
} else {
resp.existsB = true;
resolve(resp);
}
});
}, // 残り記述

のようにして、記述し、catch処理に渡したい値をrejectすると良いかと思います。但し、

promise.then(callbackA).then(callbackB).catch(errorback).then(callbackC)

のような形でcatchの処理が実行される場合、callbackAでエラーが発生しても.catch以前の.thenの処理は行われず、次の.then関数に処理が移ることに注意が必要です。

また、bulkrequest.jsonを使いたいとのことですが、全く問題ないと思います。
そちらの通信の.thenや.catchの処理をコールバック関数内で書いてもいいですし、書かずにreturnすることで一番最初のPromiseの.then関数につなげるというのも良いかと思います。

cstap

瀧ケ平様

 

ご連絡遅くなりすいません。

こちら現状試す時間がちょっと無くなってしまったので、時間見つけて試した際にまた不明点でたらご質問致します。(こちらのスレッド?トピック?で返します)

cstap

瀧ケ平様

 

ご無沙汰しております。

再度、教えて頂いてもいいでしょうか。

 

やりたきことは下記の通りです。

・一覧から300件ほどのレコードを取得

・Aアプリ、Bアプリに共にupsert処理を行いたい

・upsertする場合には、records.jsonを用いて、一括登録をしたい。

 

上記のような処理をpromiseで書く場合(書く必要性があるかも知りたいです)、

どのような記述をしたらいいのかご教授頂ければ幸いです。

宜しくお願い致します。

鈴木さん

そのような処理であれば単純にkintone.apiのthenチェーンで処理をすればよいかと思います。

var insert = [], update = [];
// pathとparamは適切なオブジェクトを代入
kintone.api(path, "GET", param).then(function(res){
res.records.forEach(function(record){
// ここで新規登録用のレコードオブジェクトをinsertに、更新用のレコードオブジェクトをupdateに作成してpushする。
});
return kintone.api(path, "POST", params);
}).then(function(res){
// 成功時の処理
return kintone.api(path, "PUT", params);
}).then(function(res){
// 成功時の処理
});

のような形で良いのではないでしょうか?
Promiseで書く必要自体はありませんが、Promiseを利用して書くことにより非同期処理の順序を意識できることやコードのネストが深くなることを防げるのでコールバックの連続で書くよりもコードの視認性があがりメンテナンス性もよくなると思います。

参考になりますでしょうか

cstap

瀧ケ平さん

ご返信ありがとうございます。

認識あってるのかと、再度相談させて下さい。

var insert = [], update = [];
// pathとparamは適切なオブジェクトを代入
kintone.api(path, "GET", param).then(function(res){
res.records.forEach(function(record){
// ここで新規登録用のレコードオブジェクトをinsertに、更新用のレコードオブジェクトをupdateに作成してpushする。
});
return kintone.api(path, "POST", params);
}).then(function(res){
// 成功時の処理
return kintone.api(path, "PUT", params);
}).then(function(res){
// 成功時の処理
});

上記8行目のthen内処理は7行目のpost処理が終わったらこの処理に移譲されてくる認識でいいでしょうか?

この場合って、return でpromiseオブジェクトを返していなさそうなんですけど、それでも問題ないのでしょうか。

 

また、forEach内でAアプリ、Bアプリに対しての重複あり・なしのチェックを行う想定なのですが、その場合の書き方は

forEach() {

  kintone.api(Apath, “GET”, param).then(function(res_a) {

    // 重複あったらupdateにpush、そうでない場合はinsertにpush

  });

  kintone.api(Bpath, “GET”, param).then(function(res_a) {

    // 重複あったらupdateにpush、そうでない場合はinsertにpush

  }); 

}

と思っているのですが、上記のように記述した場合に

insert or updateが100件になった場合に、post or putしなければいけないなーっと思ってますが、

イマイチ実装イメージが湧いておらず・・・。

 

他とのpromiseの整合性というか、処理の整合性が難しそうなのが悩んでいるのですが

どのように解決したらいいのでしょうか?>。<

鈴木さん

kintone.apiメソッドはコールバックを省略した場合kintone Promiseオブジェクトをreturnするので問題ありません。

正直おっしゃっている意味や実装したい意図が良く理解できていないのですが、単純に最初の取得をbulkRequestを利用して取得して行えば重複確認処理は最初のPromiseの.thenコールバック内で行えるのではないでしょうか?

またinsert/updateが100件になった場合にpost/putを行うのではなく、insert/updateにレコードをすべて登録し終えた後にinsert/updateの中身を100件ずつpost/putする処理を書いた方が分かりやすいと思います。
また、この処理を行う場合ネストが深くなりがちなので、

var insResolve, upResolve;
var insPromise = new Promise(function(resolve){insResolve = resolve;});
var upPromise = new Promise(function(resolve){upResolve = resolve;});

insPromise.then(function(){
var insertSplited = [];
for(var i=0;i < insert.length;i += 100) {
insertSplited.push(insert.slice(i, i + 100));
}
return insertSplited.map(function(records) {
return function() {
return kintone.api(path, "POST", {app: appId, records: records});
};
}).reduce(function(pre, cur){
return pre.then(cur);
}, kintone.Promise.resolve());
}).then(function() {
console.log("すべてのinsert処理が終了しました");
});

upPromise.then(function(){
var updateSplited = [];
for(var i=0;i < insert.length;i += 100) {
updateSplited.push(update.slice(i, i + 100));
}
return updateSplited.map(function(records) {
return function() {
return kintone.api(path, "PUT", {app: appId, records: records});
};
}).reduce(function(pre, cur){
return pre.then(cur);
}, kintone.Promise.resolve());
}).then(function() {
console.log("すべてのupdate処理が終了しました");
});

のように先に記述しておき、すべてinsert/updateにpushし終えた後にinsResolve/upResolveを呼び出せばinsert/updateに入れ終えた後にそれぞれの処理を実行する処理を書いてもネストが深くなるのが防げます。

参考になりますでしょうか

cstap

瀧ヶ平様

 

ありがとうございます。

ご返信遅くなりましたが、無事実装出来ました。

ありがとうございました!