REST APIの結果が不安定

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

掲題に関しまして困っており、ご助言頂ければと思います。

現在、アプリAのrecord.edit.submit時に以下の処理を行おうとしているのですが、上手くいくときと失敗する時があり、結果が安定しません。

 

1、query:{name:アプリB}でアプリ情報をGETしアプリIDを取得

2、1のアプリIDを使っアプリBをGET

3、2の結果を使って更新データを作成しPUT(revisionも指定)

4、1のアプリIDを使ってアプリBをGET(2とは絞り込み条件が異なり、revisionを再取得する必要がある)

5、4の結果を使って更新データと追加データを作成し、PUT/POST

 

 

コードは以下です。

kintone.events.on(‘app.record.edit.submit’, function(event) {
var record = event.record;
//1.アプリID取得
getAppId(“アプリB”).then(function(appId) {
//2.アプリBのテーブルの値を指定してGET
var recId = event.recordId;
var getQuery1 = ‘レコード番号 in ("’ +recId+ ‘") and フラグ in (“no”)’;
var getBody1 = {app: appId, query: getQuery1};

get(appId,getBody1).then(function(resp1) {
console.log(resp1);
//更新データ作成
var putBody1 = {
“app”: appId,
“records”: データ
};
//3.更新
put(appId,putBody1).then(function(put1_result) {
//4.アプリBのフィールドの値を指定してGET
var table = record[‘テーブル’].value;
var bbCode = record[‘BB’].value;
var 配列 = [テーブルの特定フィールドの値を入れる];
var getQuery2 = ‘aaコード in (’ + 配列 + ‘) and bbコード = "’ + bbCode + '" ';
var getBody2 = {app: appId, query: getQuery2};

get(appId,getBody2).then(function(resp2) {
console.log(resp2);
var putArray = [];
var postArray = [];
var tableCopy = table;
var denDate = record[‘伝票日付’].value;
var denNo = record[‘伝票番号’].value;

//更新データ作成
var putBody2 = {
app: appId,
records: データ
};
//5-1PUT
if(条件によってPUTのみかPOSTもするか変わる){

put(appId,putBody2).then(function(put2_result) {
console.log(put2_result);
return event;
});
//5-2POSTデータ作成後PUT/POST
}else{
put(appId,putBody2).then(function(put2_result) {
console.log(put2_result);
//POSTデータ作成
var postBody = {
app: appId,
records: データ
};
post(appId,postBody).then(function(post_result) {
console.log(post_result);
});
});
}
});
});
});
});
return event;
});

 

//***GET***
function getLocStock1(appId,body){
return kintone.api(‘/k/v1/records’, ‘GET’, body).then(function(resp) {
return resp;
}, function(error) {
console.log(error);
return error;
});
}

//***PUT***
function put(appId,body){
return kintone.api(‘/k/v1/records’, ‘PUT’, body).then(function(resp) {
return resp;
}, function(error) {
// error
console.log(error);
return error;
});
}

//***POST***
function post(appId,body){
return kintone.api(‘/k/v1/records’, ‘POST’, body).then(function(resp) {
return resp;
}, function(error) {
console.log(error);
return error;
});
}

 

上記処理を行おうとすると、最後(更新、追加内容がアプリBに正しく反映される)まで成功することもあれば、1のアプリIDの取得でエラーになったり、5-2POSTでエラーが検出されるが追加データの内1レコード分だけ反映されていたりと、結果が安定せず困っています。

また、resp/errorでerrorの際はconsole.log()に内容を表示するようにしているのですが、console.log()に中身がなく、errorの原因がわかりません。

以上、何卒宜しくお願い致します。

処理の内容は見てないのですが、原因はsubmit後、処理が終わる前に保存完了(具体的にいうと保存完了しcreate画面からshow画面へページ遷移)しているからです。なので、タイミングによってうまくいくときといかないときがあって「不安定」に見えてると思います。

apiをつかってデータを出し入れしようとするとき、処理は非同期ですのでこのようになってしまうんですね。なので、ちゃんと処理がおわってから保存完了をしてもらう、というのを意図的に実装する必要があります。

 

具体的にはkintone promiseを使うことです。下記にあるように、submit時にapiを使ってなにかやる場合はpromiseをつかってあげる必要があります。

https://cybozudev.zendesk.com/hc/ja/articles/204564604-kintone-API-%E3%81%A7-Promise-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86-

 

上記のサンプルコードを例にpromiseを使って実装してみてください。

あ、誤解してました。.then() をつかわれていますので、promiseをもちいた書き方はされていますが、

promiseをreturnしてないのと、resolveやrejectなどで処理が終わったことを通知してないからうまく動作していなさそうですね。

先述したサンプルにあるように、

Promiseをreturnしている
処理完了後にresolveをしている

という点に着目して修正してみてください。

村濱様

コメントありがとうございます。

上のコードではあまりにも量が多くなってしまったため省略してしまったのですが、

アプリBへのGET/PUT/POSTではPromiseを利用したモダンなアプリの全レコード取得の書き方を参考にさせて頂き、500件以上(PUT/POOSTは100件以上)の処理をつけています。

例えばPUTだと以下のような感じです。

function put(appId,body,opt_resp){
var allResp = opt_resp || [];
return kintone.api(‘/k/v1/records’, ‘PUT’, body).then(function(resp) {
allResp = allResp.concat(resp);
//更新件数が100件以上の場合は100件ごとに区切って繰り返す
if(resp.records.length === 100){
var newBody = {
“app”: appId,
“records”: body.records.slice(100)
};
return put(appId,newBody,allResp);
}
return allResp;
}, function(error) {
console.log(error);
return error;
});
}

この処理を作る際に、私もPromiseについて色々とみてみたのですが、

return new kintone.Promise(function(resolve, reject){

resolve(event);

});

のものと、上のPUT方法があります。

あまりjavascriptに詳しくないため、おかしな事を言っていたら申し訳ないのですが、上記の方法では

Promiseとして機能していない?のでしょうか。

 

 

宜しくお願い致します。

村濱様

二つ目に頂いたコメントを見ずに返信してしまいました。。

載せて下さったリンク先をもっとよく確認して、修正してみます。

 

> 上記の方法ではPromiseとして機能していない?のでしょうか。

Promise自体は使われてるんですが、ここで問題になってるのは、kintone Apiに対して、promiseをreturnしていないのと、resolveされてないこと、の2点にあります。

コードで指し示すと下記のような感じです。

kintone.events.on('app.record.create.submit', function(event) {
// promiseをreturnしてあげればkintoneは処理が終るのを待つ
return new kintone.Promise(function(resolve, reject) {

// < 処理したい内容をここにかく>

// 最後にresolveで処理が終わったという通知をしてあげる
resolve(event);
});
});

 

下記は余談です。無視してもらって結構です。
JavaScriptは今回のケースの用に非同期が原因でとっつきづらいケースが多々あります。まず、どういうときに非同期になるか、ということですがkintoneを使っていて一番あるのは通信するときです。kintoneのAPIをつかってデータをとるだけでなく、他のサーバーからデータをとろうとするとこうなってしまいます。(その他、ファイルの入出力を行うときも非同期です)
逆になぜ同期しないのか、という点を申し上げると、JavaScriptの仕組み上、同期をしてしまうと、処理を待っている間ブラウザが固まってしまうことになって、ユーザビリティがかなり落ちるんですね。なのでデータ通信など時間がかかるものは非同期でやってあげることになっています。(方法としては無理やり同期する方法は実はありますが非推奨なのでココでは述べません)

じゃあ、非同期処理ではAが終わったらBをする、というような処理はどうやっていたかというとCallbackという仕組みをつかっていたんですね。でもそれはそれで面倒なので、近年になってPromiseというのが実装されはじめました。これを使えば、非同期処理であってもAがおわったらBをする、ということが比較的簡単にできるようになりました。Promiseの使い方、仕組みを学ばないといけない、という点では相変わらずとっつきにくいのかもしれませんが。

村濱様

 コメントありがとうございます。

return new kintone.Promise(function(resolve, reject){

1~5の処理

resolve(event);

});

とすることで、当初(1)のGETからerrorになったり、一件だけPOSTされていた問題が解消されました。

ただ、今度はPUT/POSTでfunction(error)に入っているのに、ちゃんとデータは更新・追加されている、という

事が多く発生しております。

そしてやはりエラーの内容がわからず、原因が不明です。。

これも同様に、処理が終わってないのに、登録完了して画面遷移してしまっているからだと思います。一番最後の処理でresolve()して上げる必要があります。

PUT/POSTは具体的にどの部分をさしますでしょうか。5-1あたりですか?

村濱様

>PUT/POSTは具体的にどの部分をさしますでしょうか。5-1あたりですか?

ご推察の通りです。5-1、5-2の部分です。

resolve()の位置ですが、”一番最後の処理でする”というのは

//5-1PUT
if(条件によってPUTのみかPOSTもするか変わる){
  put(appId,putBody2).then(function(put2_result) {
    console.log(put2_result);
    resolve (event);   <---------------------------------------------ここ
  });

//5-2POSTデータ作成後PUT/POST
}else{
  put(appId,putBody2).then(function(put2_result) {
    console.log(put2_result);
    //POSTデータ作成
    var postBody = {
      app: appId,
      records: データ
    };
    post(appId,postBody).then(function(post_result) {
      console.log(post_result);

      resolve (event);   <---------------------------------------------ここ
    });
  });
}

この位置という認識でよろしいでしょうか。

そうですね、5-1および5-2がこの処理のゴールならば、そこにresolveを書いといてあげればうまくいきそうではあります。(そこ以外に書いたresolveは削除してください)

また、エラーで処理をとめたい(レコードの登録をさせたくない)場合はrejectを使うとできます。下記記事が参考になります
https://www.joyzo.co.jp/blog/1155

村濱様

上記の位置に resolve (event); を入れたところ、今までの様なエラー(失敗)はなくなりました!

 rejectの部分参考にさせて頂きます。

ただ、新たな問題が発生しました。

edit.submit後にテーブルの行がすべて消えてしまうようになりました。

イベント発生直後と resolve (event); の直前に console.log(event); で確認すると、テーブルのlength:0で

テーブルデータが存在しないことになっている?ようです。

ですがPUTやPOSTではテーブルの値や長さを利用できました。

 

 

> イベント発生直後

うーん、なんだろう…
edit.submitのイベント発生時点でデータを参照して表示されてないのは別の原因がありそうですね。
別のスクリプトも動かしているとか…

村濱様

edit.submit時に採番を行うスクリプトがあります。。

これが原因でしょうか?

あやしいですねー。そのスクリプトを外してみて、正常に動くならばそれが原因かとおもいますね。。

スクリプトを外してみましたが、やはりテーブルデータが消えてしまいました。

アプリBの更新データを作成する際にテーブルの値が必要で、それはきちんと反映されている(テーブルの値がアプリBで確認できる)ので、少なくともedit.submitの開始時にはテーブル値も存在していると思われます。

では、edit.submit直後は値持っていて、すくなくとも途中まで値持ってるっていうことですね。

> var tableCopy = table;

ここが怪しそうですね。実際のスクリプトでは、tableCopyのデータをいじったりしてしまってませんか?
実際にはそれはコピーじゃなくて同じ実態を参照しているだけ(参照渡し)ですので、tableCopyを操作するとevent.record[“テーブル”].valueにも影響が及びます。最後にそれがresolve(event)されてるので操作された値が渡されている可能性があります。

値のコピーをとって安全に操作する場合は、下記の用にして値をコピーしてください。
http://qiita.com/rentondesu/items/f3e8924af2fcd3c28598

※IEでなくてもいいなら、Object.assign()による値コピーがいいです。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

村濱様

ご報告が遅くなり申し訳ありません。

tableCopyのデータをfor文で一件ずつ抜いている部分があり、それが原因で最終的にテーブル行が全部削除された状態でreturn eventされ、行がなくなっていました。

添付して下さったサイト等を参考にし、値のコピー方法を変えたところテーブルデータに変更なくレコード保存が出来ました。

 

この度はありがとうございました。

 

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

やはりtableCopyの部分が問題だったんですね。解決してよかったです。