データベース内の5,000レコードを更新するAjax呼び出しがあるため、これには多くの時間がかかります。何かが起こっていることを示すAjaxの「読み込み中の画像」がありますが、「5000の50を更新しています。.」、「5000の200を更新しています」などを表示するためのより良い方法を探しています。
5000の異なる投稿を行わずにAjax/jQueryでこのようなことを行うための最良の方法は何ですか?
私が思うに最も良いのは Comet を使用することです。
コメットスタイルのアプリケーションでは、サーバーは基本的にデータをクライアントにプッシュできます(クライアントがサーバーからデータを何度も要求する代わりに)。クライアントはサーバーに1回接続するだけで済みます。その後、サーバーはデータをクライアントにプッシュし続けます。
ウィキペディアから:
Cometは、クライアントがデータを要求しなくても、Webサーバーがデータをクライアントに送信できるようにするプログラミング手法です。これにより、ブラウザーでホストされるイベント駆動型Webアプリケーションを作成できます。
それでは、Cometがどのように機能するかを見てみましょう。次のサーバーサイドコードを参照してください。ここではwhile
ループが使用されていますが、代わりに独自の条件を設定できます。 whileループでは、ページは日時を書き込み、フラッシュしてから1/2秒間スリープします。
ASP.NETページのコードビハインド:Service.aspx.cs
public static string Delimiter = "|";
protected void Page_Load(object sender, EventArgs e)
{
Response.Buffer = false;
while (true)
{
Response.Write(Delimiter
+ DateTime.Now.ToString("HH:mm:ss.FFF"));
Response.Flush();
// Suspend the thread for 1/2 a second
System.Threading.Thread.Sleep(500);
}
// Yes I know we'll never get here,
// it's just hard not to include it!
Response.End();
}
クライアントサイドコード-純粋なJavaScript
リクエストは1回だけ行い、XMLHttpRequest
のreadyState === 3
のデータを確認し続けます。
function getData()
{
loadXMLDoc("Service.aspx");
}
var req = false;
function createRequest() {
req = new XMLHttpRequest(); // http://msdn.Microsoft.com/en-us/library/ms535874%28v=vs.85%29.aspx
}
function loadXMLDoc(url) {
try {
if (req) { req.abort(); req = false; }
createRequest();
if (req) {
req.onreadystatechange = processReqChange;
req.open("GET", url, true);
req.send("");
}
else { alert('unable to create request'); }
}
catch (e) { alert(e.message); }
}
function processReqChange() {
if (req.readyState == 3) {
try {
ProcessInput(req.responseText);
// At some (artibrary) length recycle the connection
if (req.responseText.length > 3000) { lastDelimiterPosition = -1; getData(); }
}
catch (e) { alert(e.message); }
}
}
var lastDelimiterPosition = -1;
function ProcessInput(input) {
// Make a copy of the input
var text = input;
// Search for the last instance of the delimiter
var nextDelimiter = text.indexOf('|', lastDelimiterPosition + 1);
if (nextDelimiter != -1) {
// Pull out the latest message
var timeStamp = text.substring(nextDelimiter + 1);
if (timeStamp.length > 0) {
lastDelimiterPosition = nextDelimiter;
document.getElementById('outputZone').innerHTML = timeStamp;
}
}
}
window.onload = function () { getData(); };
SESSION変数で大きな更新レコードを実行している関数に、単一の(または非常に多くの)更新ごとの現在の進行状況を許可し、個別のAJAXスクリプトを使用して、この進行状況の値を取得します。 SESSIONを実行し、JavaScriptでこれを使用してプログレスバー/テキストを更新します。
nミリ秒ごとに1回Ajaxコールバックを起動し、実行された量(更新されたレコードの数など)を照会し、それを使用して進行状況を表示しますバー。方法のようなもの this は機能します。
現在、バッチ更新のすべてのレコードに1つのPOSTを使用しており、呼び出しと戻りの間に読み込みイメージを配置していると想定しています。
サーバーが更新を完了するまで戻るのを待つのではなく、そのバッチ更新の特別なIDを使用してサーバーをすぐに返すようにします。次に、バッチ更新のステータスを返すサーバー呼び出しを実装します。これを進行状況ダイアログで呼び出して、進行状況を報告できます。
var progressCallback = function(data){
//update progress dialog with data
//if data does not indicate completion
//ajax call status function with "progressCallback" as callback
});
//ajax call status function with "progressCallback" as callback
進行状況に応じて応答バッファーを更新し、サーバーから応答バッファーを定期的にフラッシュすることができます。
ただし、xhttpr
を介してリクエストが完了する前にリクエストを読み取るのに問題がある場合があります。 iframeを介してリクエストを行い、「httpストリーミング」を介してその読み込みを進行させることができる場合があります。
しかし、それでも大ざっぱなことがあります。 HTTPは、断片的/断片化されたものを転送することを意図したものではありません。他の人が指摘しているように、操作のステータスを取得するには、後続の呼び出しを個別に行うのが最善です。
返事を読んでいるうちに思いついた。
JavaScriptとPHP共有Cookie、
利点:
単にSQLステートメントを実行するのではなく、各レコードを個別に反復する理由があると思います。
その場合は、200回程度の反復ごとにajax呼び出しを行うだけです。 200レコードのグループごとにこれを行うと、50回のAjax呼び出ししか消費しません。
(擬似コード)のようなもの:
If iterationNumber mod 200 == 0
// Make Ajax call.
投稿先のサーバー側が何であるかはわかりませんが、この方法はほとんどのプログラミング言語に適用できるはずです。例としてPHP)を使用しています。
HTML側には、プログレスバーを指定された幅に更新する関数があります。この例では、この関数 'setProgress'を呼び出しています。この関数は、進行状況バーを更新するために番号を取ります。
サーバー側のコードで、更新のチャンク(たとえば、反復ごとに100)を実行し、出力を生成します。反復ごとにjavascript呼び出しを出力することにより:
<?php
while () { // Whatever your loop needs to be.
// Do your chunk of updates here.
?>
<script type="text/javascript">
setProgress(100);
</script>
<?php
flush(); // Send what we have so far to the browser so the script is executed.
}
setProgress(5000); // All done!
?>
これをエコーした後、出力バッファーをフラッシュして、このデータがブラウザーに送信されることを確認します。これは完全なスクリプトタグであるため、ブラウザは内部でjavascriptを実行し、プログレスバーを渡した値に更新します。
すべてを機能させるには、プログレスバーに呼び出している番号を理解するためにいくつかの計算を追加する必要がありますが、それはそれほど問題にはならないはずです。わかりやすくするために必要な計算を避けたかったのですが、例のsetProgressでパーセンテージを使用する方がおそらく理にかなっています。
次のような単純なテーブルを作成します。
CREATE TABLE progress_data (
statusId int(4) NOT NULL AUTO_INCREMENT,
progress float DEFAULT NULL COMMENT 'percentage',
PRIMARY KEY (id_progress_data)
);
JQueryコード:
//this uses Jquery Timers http://plugins.jquery.com/project/timers
$('#bUpdate').click(function() {
//first obtain a unique ID of this operation - this has to by synchronized
$.ajaxSetup({'async': false});
$.post('ajax.php', {'operation': 'beginOperation'}, function(data) {
statusId = parseInt(data.statusId);
});
//now run the long-running task with the operation ID and other params as necessary
$.ajaxSetup({'async': true});
$.post('ajax.php', {'operation': 'updateSite', 'statusId': statusId, 'param': paramValue}, function(data) {
$('#progress_bar').stopTime('statusLog'); //long operation is finished - stop the timer
if (data.result) {
//operation probably successful
} else {
//operation failed
}
});
//query for progress every 4s, 'statusLog' is just the name of the timer
$('#progress_bar').everyTime('4s', 'statusLog', function() {
var Elm = $(this);
$.post('ajax.php', {'operation': 'showLog', 'statusId': statusId}, function(data) {
if (data) {
//set bar percentage
$('#progress').css('width', parseInt(data.progress) + '%');
}
});
});
return false;
}
バックエンドコード(PHP):
if (isset($_POST['operation'])) {
ini_set("display_errors", false);
session_write_close(); //otherwise requests would block each other
switch ($_POST['operation']) {
/**
* Initialize progress operation, acquire ID (statusId) of that operation and pass it back to
* JS frontend. The frontend then sends the statusId back to get current state of progress of
* a given operation.
*/
case 'beginOperation': {
$statusId = //insert into progress_data
echo json_encode(array('statusId' => $statusId));
break;
}
/**
* Return back current progress state.
*/
case 'showLog': {
$result->progress = (float) //SELECT progress FROM progress_data WHERE statusId = $_POST['statusId']
echo json_encode($result);
break;
}
case 'updateSite': {
//start long running operation, return whatever you want to, during the operation ocassionally do:
UPDATE progress_data SET progress=... WHERE statusId = $_POST['statusId']
}
}
}
/* Terminate script, since this 'view' has no template, there si nothing to display.
*/
exit;
私はすでに3つのアプリケーションでこのアプローチを使用しており、非常に信頼性が高く、高速であると言わなければなりません(showLog操作は単純なSELECTステートメントです)。セッションを使用して進行状況を保存することもできますが、セッションを書き込みで閉じる必要があるため(ファイルに保存されている場合)、それ以外の場合はshowLog AJAXクエリは、長い操作が終了するのを待ちます(そしてルーズセンス)。
私はかつてこのように似たようなことをしました( Zain Shaikh に似ていますが、もっと簡単です):
サーバー上
int toUpdate = 5000;
int updated = 0;
int prev = updated;
while(updated < toUpdate)
{
updated = getAlreadyUpdatedRows();
flushToClient(generateZeroSequenceOfLength(updated - prev));
prev = updated;
sleep(500);
}
closeStream();
クライアント上
Zain Shaikh パスに従いますが、ProcessInput
では、更新するレコード数とinput.length
の比率に応じてプログレスバーのサイズを変更します。
このソリューションは、多くの場合、クライアント側の複雑さをネットワーク帯域幅と交換します。
何をしているかを本当に理解していない限り、このサーバーアクティビティを最終的なファイルインポートと混在させないでください。安定性のためにdbクエリ(更新された行をカウントするための選択)を交換します:ユーザーがページを変更した場合はどうなりますか?プログレスバーは問題ありませんが、インポートは完了しません。
何か怪しいようです。
フラッシュで5000レコードを更新できないデータベースに精通していません...つまり、これは適用している単一の更新ではなく、レコードごとの更新ですか?
ユーザーが5000エントリをダウンロードでき、どのエントリが編集されたかをマークせず、更新の最後に1つの更新ボタンを適用して5000レコードすべてを何らかの方法で戻す必要があるシステムについて考えてみます。それは最悪の場合です。
したがって、待機時間がないように問題を分割する方法があるかもしれません。たとえば、一時的なデータベーステーブル(またはアプリケーションメモリ内で、ORMエンティティのリストを作成するだけで非常に簡単です...しかしそれは別として)を考えてみてください。そうすれば、それらの更新をコミットするときに、少なくともクライアント/サーバー転送を必要としないバッチ。編集された個々のフィールドにマークを付けることも可能であるため、変更されたもの以外はDBで更新されません。
実行時間の長いプロセスがあり、誰かが提供したものを使用しなければならないことが時々あることを私は知っています...しかし、おそらく少し考えれば、単に待機時間を取り除くことができます。
ロード中の進行状況を表示するために、バックエンドを変更して、選択的なロードを実行できるようにします。
例えば、
var total_rec = 5000;
var load_steps = 20;
var per_load = total_rev / load_steps;
var loaded = 0;
while (loaded < total_rec) {
www.foobar.com/api.php?start=loaded&end=(loaded+per_load);
loaded += per_load;
}
ロードが完了するたびに、プログレスバーを更新します。
バックエンドを変更する別の方法は次のとおりです。
www.foobar.com/api.php?start=loaded&count=50