アプリ版:「スタンプのみでお礼する」機能のリリースについて

innerHTMLやinnerTextでの反映タイミングについて教えてください。

JavaScriptでsleep関数の自作テストを行なっている過程で
innerHTMLやinnerTextが即時には反映されない現象に直面しました。
コードを貼り付けておきます。
※htmlとしてIE等のブラウザで動かしてもいいですが、
htaとして実行したほうが簡易で軽くていいと思います。

このコードでは
(1)innerHTML(かinnerText)で「実行中…」を表示
(2)sleep関数を呼ぶ
(3)innerHTML(かinnerText)で「終了」を表示
という、(少なくとも表示部分に関しては)
極めて単純な処理をしています。しかし(1)が反映されません…。

(1)と(2)の間にalertを書いたりするときちんと(1)が表示されます。

よく他言語・フレームワークではflushとかリアルタイム描画等の
コマンドやオプションで対処できるので、JavaScript(JScript)でも
ないものかなぁと思い質問してみることにしました。


質問は以下です
・この現象はなぜ起こるのか。原因説明
・これを回避する方法はあるかないか
・あるのならその方法

明示的なコマンド・オプションでも対症療法でもかまいません。
環境はとりあえずはWinXP・hta(レンダリングエンジン:IE8)で動けばOKです。
よろしくお願いします。


<html>
<body>

<div id="status">sleepテスト</div>

<input type="button" onclick="testAjaxSleep()" value="testAjaxSleep"><br>
<input type="button" onclick="testBusySleep()" value="testBusySleep"><br>
<br>

<script>
var waittime = 3;

function testAjaxSleep() {
document.getElementById("status").innerHTML = "「AjaxSleep」実行中…";
ajaxSleep(waittime);
document.getElementById("status").innerHTML = "AjaxSleep終了";
}
function testBusySleep() {
document.getElementById("status").innerText = "「BusySleep」実行中…";
document.close();
busySleep(waittime);
document.getElementById("status").innerText = "BusySleep終了";
}


function ajaxSleep(sec) {
var stopTime = (new Date()).getTime() + Math.floor(1000 * sec);
var url = "http://www.google.com/?dummy";
var xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
while (stopTime > (new Date()).getTime()) {
xmlHttp.open('GET', url, false);
xmlHttp.send(null);
}
}
function busySleep(sec) {
var stopTime = (new Date()).getTime() + Math.floor(1000 * sec);
while (stopTime > (new Date()).getTime()) {}
}
</script>

</body>
</html>

A 回答 (7件)

 お礼書いてくれたので、蛇足となりつつも



AJAXでサーバーの応答待つのは、あまりにも不正確になりがちで、
処理待ちの間ブラウザごとロックするブラウザーもあるそうです。

そこで、HTAでやってるって事なんで、wscriptshellのsleepを使って
もう一つ似たようなやつを↓

(sleep.js)
var parm = WScript.arguments;
if (parm.length > 0) {
WScript.Sleep(parm(0)*1000);
}
を準備して

HTA内からajaxSleep()で呼び出す、

function ajaxSleep(sec){
var WshShell = new ActiveXObject("WScript.Shell");
WshShell.run("wscript sleep.js " + sec , 0 ,true); //同期
WshShell = null;
}

※実際は固定のパス名(sleep.jsの保存場所)も指定しときゃなきゃいけないけど。
    • good
    • 0
この回答へのお礼

おお。おおおおぉおぉ。
きちんと(1)を実行してから(2)で待ち、その後に(3)が表示されることを確認しました。すばらしいです。

通常のWebや他のブラウザでは動かないものの、わたしは「とりあえずはWinXP・htaで動けばOK」というニーズだったので、これを完全にクリアしているわたしの求めていた結論といえます。(もともとの質問とは少しずれましたが)

余談ですが、私はhtaはできればhtaファイルそのもの1ファイルのみで完結するほうが望ましいと思っているため、wscript用に別ファイルを用意するのではなく、実行時にそのファイルを作成し、onunload時に削除するという方法をとろうかと思っています。

意外なことに新たなsleep実装方法を得ることが出来、大きな収穫となりました。ちょっと待っていてよかったです。
ご回答ありがとうございました。

お礼日時:2010/12/12 18:03

他の言語なら、sleep関数があるから、


これこそ AjaxSleep()関数 だ!

<div id="hoge"></div>
<script type="text/javascript">

 function AjaxSleep(sec){
  var XHR = new XMLHttpRequest();
  XHR.open("GET","sleep.php?timesec=" + sec,false); //同期通信にする!
  XHR.send(null);
  if(XHR.responseText==="complete") XHR = null;
 }

 document.getElementById("hoge").innerHTML = "hoge";
 //3秒待つ
 AjaxSleep(3);
 //
 document.getElementById("hoge").innerHTML = "fuga";
</script>
</body>


sleep.phpの中で、
<?php
header('Content-Type: text/plain; charset=UTF-8');
if(isset($_GET['timesec'])){
usleep($_GET['timesec'] * 1000000);
}
echo 'complete';
?>
    • good
    • 0
この回答へのお礼

これは…No.1で私が記述した
「指定秒数sleepしてからレスポンスを返すサーバへアクセスする方法」
に該当しますね。

このタイプの擬似sleepがもっともきれいな仕組みで出来そうですよね。
「JavaScript単体で実現させたい」という希望に対応することは、
難しそうだということはわかりました。

サーバーを使用しsleepする方法を検討してみます。
ご回答ありがとうございました。

お礼日時:2010/12/10 11:47

>待っている間に他の処理をしてもらっては困ります。



はい。
whileでのsleepのエミュレートは「処理をしない」です。
つまり、画面の描画も行いません。
画面の描画という「sleepの処理以外の処理」を行っていませんから、正しい動作です。


なお、「正しい動作」と「期待する動作」は意味が違いますので、あしからず。


将来的にはE4XやGecko定義のJavaScript、JScriptなどでCなどのsleepと同じ物が作られるかもしれませんが、
現段階では他の動作(画面描画)をさせたい場合は、setTimeoutなど、他の処理が出来る方法を使わなければなりません。

ブラウザには処理をさせるが、利用者には処理をさせないというのであれば、
ドキュメント全体で入力を受け付け、sleep中はその処理をキャンセルするしかないですね。
<form onsubmit="return false;">
のような方法です。(他にDOMを使った方法もありますが)


どうしてもsleepを使いたいのであれば、多少強引かもしれませんが、ご参考に。
未検証です。

function testBusySleep() {
document.getElementById("status").innerText = "「BusySleep」実行中…";
setTimeout(function(){
busySleep(waittime);
document.getElementById("status").innerText = "BusySleep終了";
}, 1);
}
    • good
    • 0
この回答へのお礼

>whileでのsleepのエミュレートは「処理をしない」です。
>つまり、画面の描画も行いません。

(2)の最中に画面描画を行なわなくてもまったく問題ありません。希望は(1)を実行してから(2)に進みたいということです。質問はその実現は出来ないのかということだったのですが、それは説明するまでもなくできないということなのでしょうかね。


画面描画を強制的に起こさせようと試行錯誤してみました。
(1)の処理部分に「resizeBy(3, 3)」と書いてみました。すると、ウインドウ枠の変更自体は即時に起こったのですが、内側のレンダリングだけが遅れ、おかしな見た目になりました。それ以外にもいくつか試したのですが解決には至りませんでした…。

ご回答のとおり、(2)にはいる前に(1)の描画を行なうことは出来ないのかもしれませんね。だいぶ諦めがついてきました。
ありがとうございました。

お礼日時:2010/12/10 13:54

誤解があるように思われますが、現行の JavaScript で sleep() 関数は「作れません」(ActionScript などを併用する試みなどはありますが、今は考えません)。



A;
sleep(10);
B;
C;

と同じことをしたければ、

A;
function callback() {
 B;
 C;
}
setTimeout(callback, 10 * 1000);

のように後続処理を全てコールバック関数に入れ、setTimeout() で遅延実行します(正確に 10 秒後にはなりませんが、今は問題にしません)。それまで使っていたコンテキスト情報をどのように渡すか、なども工夫して下さい。

要するに、sleep() を持つ言語系なら内部的にやっている継続渡し的なことを、JavaScript では自前で管理しなければならないということです。それゆえ、上から手続きをだらだら並べるのではなく、きちっと処理単位ごとにブロック化(関数化)する必要があります。まあ、この辺はイベント駆動のコードを弄った経験があればすぐに慣れると思いますし、JSDeferred などのライブラリもあります。

> while (stopTime > (new Date()).getTime()) {
言葉が悪いですが、これは一種のブラクラですので、絶対にやらないで下さい。

※余談ながら、HTML4/5 で html 要素、body 要素の開始タグと終了タグは省略可なのでサンプルでは不要です。文法的な意味で絶対に必要なのは title 要素の開始・終了タグです。
※Msxml2.XMLHTTP でバージョンを省略すると 3.0 が使われます。.NET Framework などが入っていれば MSXML 6.0 が入っている可能性が高いと思います。
    • good
    • 0
この回答へのお礼

ご提示いただいた方法は私がNo.1の補足で書いた内容ですね。
# (3)に当たる処理を関数にして(2)でsetTimeoutする
# ということをすれば似た現象を引き起こせると思いますが
# 要件を満たせませんし、そもそも関数の自作でもありません。

今回のサンプルのajaxSleepであればまぁまぁいけていると思っていたのですが、JavaScriptでの完全な「sleep関数の自作」は難しいようですね。
ご回答ありがとうございました。

お礼日時:2010/12/10 13:46

同期通信はsendを行った後、画面描画や利用者(マウス操作やテキストエリアなどへのキー入力など)の処理を行わずにサーバーからの応答を待ち続けます。


質問文中のコードでは、応答があった直後はまだwhile()の中にあるため、そのまますぐに次のopen(同期通信)を行います。

whileでsleepをエミュレートする方法は、あくまで「一定時間待つ」だけであって、待っている間に何かの処理をする(処理が出来る)という物ではありません。
画面描画や利用者の処理を待つ場合は、いったん関数を抜けるか、alert()などでブラウザに処理を渡す必要があります。
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。
一点確認させてください。

>whileでsleepをエミュレートする方法は、あくまで「
>一定時間待つ」だけであって、待っている間に
>何かの処理をする(処理が出来る)という物ではありません。

そもそもsleepですので処理を待つことが目的です。
待っている間に他の処理をしてもらっては困ります。

ただし、sleepの前の処理は行なってから
待ちを開始してほしいということです。

(1)-(3)の例で言えば
(1)を行なってから(2)をしてほしいと、当然とも思える
ただそれだけの希望です。
ところが実際には、(1)が反映される前に
(2)に進んでしまっているように見えることが問題なのです。


(1)を画面上に反映させてから(2)に進ませるということを、
提示しているプログラムの機能を損なうことなく実現させることは
事実上不可能だというご回答だと理解してよろしいでしょうか?

よろしくお願いします。

お礼日時:2010/12/08 19:17

というか、この質問もしかして、


Ajaxリクエストが完了するまで、”実行中”と表示しておいて、
完了したら”完了”と表示させたいだけなのかしら?

 だとしたら、変なsleep関数作らなくても、readystateの監視だけでも
でけそうな...
    • good
    • 0
この回答へのお礼

# だとしたら、変なsleep関数作らなくても、
# readystateの監視だけでもでけそうな...

これを読んで、「そういえば普通のajaxアクセスの場合にはonreadystatechangeで表示変更をしたことがあるなぁ」と思って調べてみました。すると、自分の書いたコードで以下の違いがありました。

今回:xmlHttp.open('GET', url, false);
以前:xmlHttp.open('GET', url);

一般的には第3引数を指定しない(もしくはtrueを指定する)から他の処理を継続できる、今回は待たせる為にあえて「false」を指定しているから処理を待ってしまう、という違いがあったことに気がつきました。
この指定の差で画面描画の状態が変わるのだということが分かり、新たな発見が出来、勉強になりました。
ありがとうございました。

お礼日時:2010/12/10 13:39

この現象はなぜ起こるのか。



=>あなたの作られたsleep関数の中で、無意味にxmlHttp.send(null)
  を繰り返しているため、スクリプトエンジンはinnerHTMLの書き換え
  指示を出す暇が無いのです。alertを入れると中断するのでその隙に
  innerHTMLを書き換えることが出来ます。


これを回避する方法はあるかないか

=>window.setTimeout/window.clearInterval、とか
   window.setInterval/window.clearInterval を
  使えば無駄なループを実行しなくても出来ます。

あるのならその方法
省略:上のsetTimeoutとかでNET検索してみてください。

この回答への補足

> =>window.setTimeout/window.clearInterval、とか
>    window.setInterval/window.clearInterval を
>   使えば無駄なループを実行しなくても出来ます。

もともとやりたかったことはsleep関数の自作です。sleep関数とは、それを(2)で呼び出すことで(3)以降の処理を待たせることができる関数です。

(1)や(3)の処理部分を書き換えなければならないのであればそもそも要件を満たしていないことになります。

私が掲げた2つの関数はこの要件を満たしています。

(3)に当たる処理を関数にして(2)でsetTimeoutするということをすれば似た現象を引き起こせると思いますが要件を満たせませんし、そもそも関数の自作でもありません。


sleep関数の自作は、おそらくsetTimeoutやsetIntervalでは実現できないのではないかと思います。sleepの自作例として、ここで掲げた2つの方法がよくネット上で見かけた方法です。

また、他にも別ページをダイアログに表示させ指定秒後に閉じることで待たせる方法や、Javaアプレットを使用する方法や指定秒数sleepしてからレスポンスを返すサーバへアクセスする方法などをネット上で見つけましたが、今回はJavaScript単体で実現させたいと思っているためそのような方法は今回はNGです。

もしも、ここに掲げている2つの自作sleep関数以外の方法でsleepが実現できるのであればそれも教えていただきたいです。
よろしくお願いします。

補足日時:2010/12/08 15:49
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。

>あなたの作られたsleep関数の中で、無意味にxmlHttp.send(null)
>を繰り返しているため、スクリプトエンジンはinnerHTMLの書き換え
>指示を出す暇が無いのです。

busySleepはその名のとおりCPUリソースを使用し続ける関数です。busySleepについてであればそうかもしれません。

しかしajaxSleepはレスポンス受信を待つ為、(マシン性能や回線速度にもよるとは思いますが)CPUはほとんど消費しません。そのajaxSleepについても同じなわけです。

CPUはほとんど消費していなくても「指示を出す暇が無い」のでしょうか?そういうものなのでしょうか?

お礼日時:2010/12/08 15:58

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!