時間がかかる操作中に表示を更新する
Dave Massy
February 26, 2001
日本語版最終更新日 2001 年 4 月 5 日
先月、DHTML から Web サービスを使用することについてお話しました。来月以降、このトピックについてもう少し詳しくお話しようと考えています。私は、Web サービスについて考えれば考えるほど、Web サービスが Web アプリケーションに今後もたらす可能性に本当に興奮しています。嘆かわしいことだと分かっていますが、私はこれらのことに興奮してしまいます。ただし、今月はもっと基本的なトピックを取り上げようと思います。ここ数か月の間に、ここで取り上げる問題点に関してやや長い電子メールのやりとりがありました。私は、この問題がなぜ起こるのか、どうすればこれを解決できるのかを説明することは興味深いことではないかと考えました。この電子メールのやりとりは、次のような電子メールから始まりました。
「私は、実行時間がかかる While
ループを持つスクリプト関数を作成しました。私は、繰り返しの進行状況を示すために、ループの内側に表示を更新するステートメントを置きました。ところが、ルーチンを終了するまで表示が更新されません。」
そのメールには、以下のようなスクリプトが添付されていました。 <SCRIPT>
function longtime()
{
var i=0;
while (i<=500)
{
d1.innerHTML="Count ="+i;
i++;
// 以下のステートメントは時間がかかる処理を表しています
var j=0;
while (j<=10000)
j++;
}
}
</SCRIPT>
<INPUT TYPE="BUTTON" onclick="longtime();" value="Click Me">
<DIV id="d1">This is where the count of iterations should display</DIV>
サンプルの表示
これは、DHTML を記述することに不慣れな人からの予期しない質問ではありません。何回か DHTML
のコーディングを経験した人にはすぐに答えられることかもしれません ("明確に理解できているでしょうか ?")
が、この問題はもっと詳しく説明されてもよいはずです。
HTML
とスクリプトに望む効果は、時間がかかる繰り返しの実行中に実行回数を表示することです。実際に生じる結果は、ユーザーにとってはかなりお粗末なものです。最も注目する必要のある問題点は、ボタンを押した後に一時停止することです。さらに、スクリプトの実行に時間がかかっていて、それを中断するかどうかを問い合わせるメッセージがユーザーに表示されます。この段階で表示が更新され、カウント値が表示されます。問い合わせが表示される前に、別の些細な現象が生じていることに気付いているでしょう。それは、ボタンが押されたままの状態になり、ユーザーはブラウザとまったく対話できなくなるという問題です。ユーザーがブラウザからロックアウトされるという現象により、スクリプトの実行に時間がかかっている状況を抜け出せるように、Microsoft®
Internet Explorer はユーザーに問い合わせを行います。
その結果、何が起こるのか ?
Internet Explorer でスクリプトを実行する基本の 1
つは、割り込みなしで実行されることです。そのため、上記のスクリプトを実行するときは、スクリプトが終了するまで、ブラウザがロックされます。最初は、この現象は設計が不十分で、バグかもしれないと思えますが、この方法で操作しない場合に予想される結果を落ち着いて考えてみると、これはバグなどではなく、むしろ論理的な設計であると確信するでしょう。
上記のスクリプトが作成者の意図通り、カウント値が 0 から 500
まで増加しながら表示されるのがユーザーに見えるように実行されると想像してみましょう。この仮説的な状況では、表示が更新されるたびに、onresize や
onchange のようなイベントが生成されることになります。このようなイベントをスクリプトですぐに処理すべきでしょうか ?
たとえば、グローバル変数の値が変更された場合に、オリジナルの関数で実行されていたスクリプトと同じ効果を持つことはおそらくできません。別の考え方として、すべてのイベントをキューに登録し、オリジナルの関数の実行の最後に処理する方法があります。しかし、これにも多くの問題があり、効果的であるとはまったく思えません。スクリプトが、画面上で事態がどのようにあるべきかを実行、判断でき、画面上の別の部分で徐々に表示を変更するのではなく、関数が完了するときに同時にすべての更新を行う方が有用です。
では、なぜスクリプトを中断することを問い合わせるときに表示が更新されるのでしょうか ? ダイアログ
ボックスを表示することにより、スクリプトが処理を明け渡すことになります。その結果、現在の DHTML ドキュメント ツリーを表示するために、ディスプレイ
エンジンが表示を更新する機会を得ることになります。スクリプトの処理の明け渡しの考え方は重要で、別の乗り物を交通の流れに合流させるために、車が道を譲ることに似ています。ブラウザでは、スクリプト
エンジンが処理を明け渡すと、ディスプレイ エンジンが処理の機会を得ます。その時点で、ディスプレイ エンジンは DHTML ドキュメント
ツリーを調べ、前回処理機会を得た以後に行われたすべての変更で表示を更新します。私は、ブラウザ内部で何が行われているかを単純化して説明しましたが、ディスプレイ
エンジンは、表示のどの部分が無効化されていて、どの部分を再レンダリングする必要があるかを判断し、次に現在の表示レイアウトを変更させ、続いて画面上のほかの部分を強制的に表示させるなど、非常に優れた処理を行っています。ここで注意する必要がある重要な点は、スクリプトが処理を明け渡すことにより、表示を更新でき、ユーザーがブラウザと対話できるようになるということです。従って、上記の関数からの例題スクリプトを希望どおりに機能させるためには、強制的にスクリプトが処理を明け渡すようにする必要があります。
スクリプトの処理の明け渡し
開発者が、スクリプトに処理を明け渡させる方法はいくつか存在します。まず、これまで見てきたように、ダイアログ
ボックスを表示することにより、スクリプトが処理を明け渡すように強制できます。しかし、上記の例ではユーザーに問い合わせることなしに画面を更新したいので、明らかにダイアログ
ボックスを使用することは望ましくありません。ただし、ダイアログ
ボックスがスクリプトが処理を明け渡すことを強制するという事実は、スクリプトのデバッグに関して興味深い問題を引き起こします。開発者は、一連のコードがなぜ意図通りに動作しないかに頭を悩ませることがよくあります。こんなとき、変数
mystuff の値を調べるために、問題のコードに次のようなコードを 1 行挿入するのが迅速で、容易な方法です。 alert("The value of mystuff is: "+mystuff);
これは、多くの環境で正しく機能します。しかし、alert はダイアログ
ボックスを強制的に表示するので、スクリプトが処理を明け渡すことになります。この処理の明け渡しにより表示が更新されることになり、レイアウト
スタイルの値の一部が変更されることになります。従って、その後のスクリプトの流れは、スクリプトに alert
が存在しない場合と必ずしも同じにはなりません。このような場合に、私がよく使用するデバッグ手法は、alert
の代わりに、次のように関心のある変数の値をブラウザのステータス バーに表示する方法です。 window.status="The value of mystuff is: "+mystuff;
この方法では、ダイアログ ボックスの表示によりスクリプトの流れが中断することはありません。
スクリプトが処理を明け渡すことを強制するもう 1 つの方法は、タイムアウトを使用することです。このスクリプトの例ではこの手法を使用します。
タイムアウト
タイムアウトは、アメリカン
フットボールでチームがゲームの流れを中断させるような奇妙な概念ではありません。これは、一定時間経過後に、スクリプトを呼び出すことができる、優れたスクリプティング技法です。setTimeout
と setInterval
という 2 つのタイマ メソッドを使用できます。2 つのメソッドは共に似た機能を達成します。setTimeout
がただ一度だけ関数をコールバックするのに対して、setInterval は適切な間隔で繰り返し関数をコールバックします。ここでは、次のように
setTimeout を使用します。 <SCRIPT>
var i;
function longtime()
{
i=0;
timedIterations();
}
function timedIterations()
{
if (i<=500)
{
d1.innerHTML="Count ="+i;
// 次のステートメントは時間がかかる計算を表しています
var j=0;
while (j<=10000)
j++;
setTimeout("timedIterations();", 1);
i++;
}
}
</SCRIPT>
<INPUT TYPE="BUTTON" onclick="longtime();" value="Click Me">
<DIV id="d1">This is where the count of iterations should display</DIV>
<SCRIPT>
var i;
function longtime()
{
i=0;
timedIterations();
}
function timedIterations()
{
if (i<=1000)
{
d1.innerHTML="Count ="+i;
// 次のステートメントは時間がかかる計算を表しています
var j=0;
while (j<=10000)
j++;
setTimeout("timedIterations();", 1);
i++;
}
}
</SCRIPT>
<INPUT TYPE="BUTTON" onclick="longtime();" value="Click Me">
<DIV id="d1">This is where the count of iterations should display</DIV>
サンプルの表示
上記の例では、理解しやすいようにオリジナルのコードを 2 つの関数に分割しています。メイン関数は timedIterations
と呼ばれ、その中で setTimeout を呼び出し、この関数が再びメイン関数を呼び出しています。ここでは、setTimeout
の周期を 1
ミリ秒に設定しています。これは、できる限り早くコールバックされるようにするためです。これらのタイマはタイミングの目的では正確ですが、ミリ秒単位まで正確であると信頼することはできません。ただし、この例でタイムアウトを
1000 ミリ秒に設定して実行してみると、かなり正確に 1
秒ごとに繰り返されることがわかります。もちろん、実行する作業に応じた繰り返しの時間を決定するために、処理をどのような単位にするかを判断するのは作成者の責任です。
ユーザーを待機させる
さて、この例は適切で、優れていますが、何か処理をしている間にその処理が完了するまで、ユーザーを待機させたい場合がよくあります。これを行う簡単な方法は、入力を禁止し、待機用のカーソルを表示することです。 <SCRIPT>
var i;
function longtime()
{
i=0;
b1.style.cursor="wait";
b1.disabled=true;
timedIterations();
}
function timedIterations()
{
if (i<=500)
{
d1.innerHTML="Count ="+i;
// 次のステートメントは時間がかかる計算を表しています
var j=0;
while (j<=10000)
j++;
setTimeout("timedIterations();", 1);
i++;
}
else
{
b1.style.cursor="";
b1.disabled=false;
}
}
</SCRIPT>
<BODY id=b1>
<INPUT id="ip1" TYPE="BUTTON" onclick="longtime();" value="Click Me">
<DIV id="d1">This is where the count of iterations should display</DIV>
</BODY>
サンプルの表示
上記の例では、繰り返しを実行している間に、ドキュメント本文全体を無効にし、待機用のカーソルを表示するために数行のコードを追加しました。そのため、ブラウザは更新を継続し、ユーザーからロックされません。ただし、ユーザーはそのページと対話できず、その結果ほかのイベントが発生することもありません。もちろんこれはかなり大雑把なアプローチです。ページのある部分だけを無効にしたいことも、ユーザーに機能を中止させる手法を提供したいこともあるでしょう。ユーザーをただ単にブロックしてはいけません。
今月のコラムのまとめとしてお話しておきたい主な点は、DHTML
である機能をコーディングする場合、ブラウザからユーザーをブロックしないことが重要であるということです。時間がかかる機能を実行しているときに、ユーザーにページ上の何も変更して欲しくないとしても、ユーザーにブラウザ全体がロックされているように感じさせることは、ユーザーにかなりの不快感を与えます。ブラウザで利用できる機能を使って、何が行われているかをユーザーにフィードバックするだけでユーザーに不快感を感じさせることがなくなります。コラムを終えるにあたって、上記の例に簡単な変更を加え、繰り返しの進行状況を示すプログレス
バーを表示する例をご紹介しておきます。
サンプルの表示
Dave Massy は、時折サングラスをかけて Dude を気取っていますが、サングラスをはずしたときは、Windows と Internet Explorer のテクニカル エバンジェリストとして働いています。彼の役目は、Microsoft のお客様がどうすればそのテクノロジを最大限に利用できるかを話すことです。Dave はテクニカル エバンジェリストになる前は、Internet Explorer チームのプログラム マネージャとして働いていました。Dave は生まれながらに英国に住んでいるので、アメリカ人の同僚にいかにトマトを正しく発音させるかという難題に果敢に取り組んでいます。
|