追記、編集しました。
追記・編集した内容メモ
(20160922)
・実行コードを再現するものに修正しました。
この記事を書いたときのは自分のテスト環境で動かしてた確認途中のものでした、、ちゃんと再現するものに差し替えました。
・症状 の内容を具体的に記述しました。
(20160924)
・コメントいただいた内容をもとに、追加で検証を行ってみました。
ブラウザ2種(FireFoxとGoogleChrome)、およびindex.htmlとajax.jsにalertを仕込んだパターンとconsole.logを仕込んだパターンと、なにもいれていない場合で挙動の違いを確認してみました。
(メモここまで)
今日イチつまったのでメモしておく。
書いてる人間
インフラ側がメインなエンジニアでアプリケーションはまったくといっていいほどです。
勉強中なので、この記事をご覧になった方に詳しい方がいたら解説いただけますと幸いです。。
起きたこと
やろうとしていること(全体)
HTMLのFormのボタンを押すことにより、フォームに入力された値をajax経由でphpのスクリプトに渡し、
処理結果をHTMLの特定箇所に書きだしたい。
ネット上で見かけたサンプルコードを動かしてみながら、処理の流れをふんふんと勉強中。
環境
クライアント:
Windows7 64Bit ブラウザはFireFoxのたぶん最新くらい
サーバ:
CentOS7 apache2.4 php5.6らへん
書いてたコード
こんな感じ。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ajaxサンプル</title>
</head>
<body>
<h1>選択</h1>
<form id="testform" name="testform" action="" method="POST">
<p>
text1: <input type="text" id="text1" name="text1" value=""><br>
text2: <input type="text" id="text2" name="text2" value=""><br>
<select id="select" name="select" size="3">
<option value="A" selected>This is A</option>
<option value="B">This is B</option>
<option value="C">This is C</option>
</select>
<input type="submit" id="execute" value="送信"><br>
</p>
</form>
<hr>
<h1>結果</h1>
<div id="loading">
</div>
<div id="result">
ここが置き換わる
</div>
<script>
alert("ページロード時に実行されるjs");
</script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="ajax.js"></script>
</body>
</html>
"loading"の部分には、処理中にあのくるくるを表示するための場所。(loading.gifみたいなのをここにいれる、今回の話題とは関係ないので割愛)
"result"の部分に、phpの処理結果をhtmlで返していれたい。
$(function () {
// 「#execute」をクリックしたとき
$('#execute').click(function () {
//formに入力された値を配列で取得
var formDataArray = $('#testform').serializeArray();
//連想配列に入れなおす
var formDataHash = {};
for (idx in formDataArray) {
var key = formDataArray[idx]["name"];
var value = formDataArray[idx]["value"];
formDataHash[key] = value;
}
//resultの中身を削除
$("#reuslt").empty();
//loadingのgifを表示
$("#loading").append("<p class='loading-gif'><img src='loading.gif'></p>");
// Ajax通信を開始する
$.ajax({
url: 'api.php',
type: 'post', // getかpostを指定(デフォルトは前者)
dataType: 'json', // 「json」を指定するとresponseがJSONとしてパースされたオブジェクトになる
data: { // 送信データを指定(getの場合は自動的にurlの後ろにクエリとして付加される)
hash: formDataHash//formに入力された値を連想配列でPHPに渡す
}
})
// ・ステータスコードは正常で、dataTypeで定義したようにパース出来たとき
.done(function (response) {
alert("done");
//resultの結果を表示
$('#result').html(response.data);
//loadingのgifを削除
$('.loading-gif').empty();
})
// ・サーバからステータスコード400以上が返ってきたとき
// ・ステータスコードは正常だが、dataTypeで定義したようにパース出来なかったとき
// ・通信に失敗したとき
.fail(function (response) {
alert("fail");
$('#result').html('fail');
});
});
});
HTMLのフォームにある
が押されたら、ajax.jsで定義されている $('#execute').click(function () 以下が走って、
フォームの値をserialaizeAarrayで取得して、連想配列に入れなおした上でapi.phpにJSONで投げる。
返ってきた結果をdoneで受け取り、#resultにHTMLとして挿入する。
Ajaxの処理に失敗したら、.failに行き、#resultには失敗したことを示すメッセージが入る。
<?php
// Content-TypeをJSONに指定する
header('Content-Type: application/json');
$hashFormData = ($_POST['hash']);
sleep(5);
$data1 = "data1:<br><table border='1'>";
foreach ($hashFormData as $key => $value) {
$data1 .= "<tr><td>" . $key . "</td><td>" . $value . "</td></tr>";
}
$data1 .= "</table>";
$data2 = "data2:";
$data = $data1 . "<hr>" . $data2;
echo json_encode(compact('data'));
ようはフォームに入力された値をdata1とdata2の形に整形して(data1はHTMLのテーブルです)、
HTMLタグの<hr>で連結して、JSON形式でajax.jsに返す。
sleep(5)は処理中であることを疑似的に再現するためのsleep(ぐるぐるの動作確認)。
※実際に動かしたいのはもっと別の処理で、あくまでこれは動作確認のサンプルです。
症状
というようなコードを書いてみて動かしてみたけど、どうにも動かない。
jsのいろんなところにalertをしかけてメッセージを表示しながら進めていると、ぐるぐるは表示されるが、phpのsleep(5)が動いてないっぽいことから、api.phpへの通信がしくってるのかな?という風に見えた。
何がいけないんだろうと思いながら、FireFoxのdevツールみてたら、例外エラーでOut of Memoryが赤く表示されていることに気づいた。
(追記)
FireBugのコンソールに表示されるエラー文字列は「uncaught exception: out of memory」と、
api.phpへのPOSTが失敗したみたいな赤字。
フォームのボタンを押すとajax.jsの処理は走るけれども、api.phpに行かずにindex.htmlが再度読み込まれる。
apacheのログにはajax.jsが304で返ってきてるんだけど、これは関係ないかな
OOM?
職業柄(というほどでもないですが)Out of Memoryといわれてぱっと思いつくのは、サーバのメモリ不足(かつかつなサーバだとMySQLが落ちる。。)。※この件では自分の勘違いです!
この程度のjsでそんなメモリ使うの?って思い、一応サーバの状態確認してみるがいうほどでもないし、/var/log/messagesにOutOfMemory的な記述もない(当然だ)。
ちょっと考えてから気づいたんだけど、FireFoxがOutOfMemoryっていってるんだから、これクライアントPC側か!!って思い、Windowsのリソースモニタ確認してみるも、こっちもそれほどでもない。たしかにFireFoxそこそこメモリ食ってましたが。
ぐぐる
助けてぐーぐる先生。
「ajax jQuery Out of Memory」とかそういう残念な感じでぐぐる。
javascriptのメモリ周りでGCやらなんやらの記事はでてくるけど、これっていう解がなかなか見当たらない中、StackOverFlowで以下の記事を発見。
uncaught exception: out of memory in Ajax Process
わー、似たような感じだっ!てみてると、以下のような記述。
I was having a similar problem while testing in Firefox.
Changing the button's type from submit to button worked for me.<button type="submit" ...>Add comment</button>
to
<button type="button" ...>Add comment</button>
え?そこ?って感じでしたが、上記自分のコードの
<input type="submit" ...>
を
<input type="button" ...>
に変えてみたら、確かにOutOfMemoryがでなくなり、api.phpへの通信も成功するようになりました。
なんで?
わかる方いたら教えてください。。。
妄想1
"submit"はすげーメモリ食うけど"botton"はそんなことない。
そんなちがいある・・・?
妄想2
ajaxだかHTMLだかjavascriptだかjQueryだかわからんがとにかくなんかバグがある。
でもそれだったら、普通にその情報でてきそう。
妄想3
OutOfMemoryと表示されていたが、実はOutOfMemoryではない(devツールの表示がオカシイ?)
。。。どれもそんなことあるー?って感じですが。。
参考
コーディング関連
はじめてAjaxを実装する人へ、効率的な学習方法について書きました
PHPとの間でAjax通信をおこなう
jQuery で Ajax を使ってみる
buttonとsubmitの違い (一応調べた)
W3C
タグとしての挙動の違いはまぁわかる。あとはまぁいろいろ過去経緯があって、ボタンタグが使えない環境があってinput typeで使い分けてたとか、いろいろあったみたい。メモリリークについてはよくわからん。。
StackOverFlow
uncaught exception: out of memory in Ajax Process
↑にもリンクしたやつ。結果的にはこれ。まぁこっちではbuttonタグを使ってますが、typeをsubmitからbuttoに変えたら直った、という点ではこれでした。
同じ症状になった、という人はちょこちょこいるみたい?
追加検証
コメントで指摘いただいた内容をもとに、実際に動かしながら以下のパターンにわけて挙動を確認しました。
確認パターン
・FireFoxとChromeでの違い
・index.htmlにalertを入れた場合と、console.logを入れた場合と、何もいれなかった場合
・ajax.jsの.fail以下と.done以下にalertを入れた場合と、console.logを入れた場合と、何もいれなかった場合
= 2 * 3 * 3 = 18パターン
確認内容
・想定する挙動になっているかどうか
・ajaxの処理はdoneかfailか
・api.phpは実行されているかどうか
・HTTPのステータスコード
・その他気になった点
補足
結論からいうと、input typeがsubmitならすべて失敗(想定する挙動ではない)となり、逆にbuttonならすべて成功でした。
さらにChromeだと、buttonでも2回更新すると1回はindex.htmlがHTTP412を返し、画面が真っ白になるっていう感じでした。。
ここらへんって、きっとお作法的によくないところもあるような気がするので今回の話題とは別のところで直すべきところがあるような気がしてますが、ご参考までに。
検証結果
ちょっと分かりずらいかもですが、、以下の通りでした。
FireFox
input type="submit" はすべてボタン押したあとにapi.phpを呼ばずにajaxは.failに行き、さらにページがリロードされる。
console.log,alertどちらも入ってれば出力される。
ただし、ajax.jsの.failの中にいれたalertは、alertのウィンドウ表示後にすぐ消えて勝手にページがリロードされちゃう。
ajax.jsが304を返す
GoogleChrome
input type="submit" はすべてボタン押したあとにapi.phpが実行されるが、ページがリロードされて結果は出力されない。
※ブラウザ上の挙動はFFといっしょだが、apacheのログからapi.phpがsleep(3)を実行後にちゃんと200を返している。
また、設定によらずブラウザを更新するたびに、2回に1回(50%ぐらい、とかではなく、更新の度に交互に)、index.htmlが412を返しページが真っ白になる。
console.logとalertは入ってればでるが、FF同様にajax.jsの.failにあるalertはウィンドウ表示後すぐページがリロードされて消える。そもそも.doneにはいかない。
Chromeに関しては、そもそもinput type="butotn"でも、上に書いた2回に1回の412でページ真っ白はでるので、
なんかもっとほかのおまじない的なものが足りてないのかもしれない。。
ここまでのまとめ
・javascriptのデバッグ、挙動の追跡にはconsole.logを使え(すいません存在は知ってましたがよくわかってませんでした)
・ajax.jsの.doneや.failの中にalertを入れるな
・input typeはbuttonを使うか、そもそもbuttonタグを使え