【JS】XMLHttpRequest.onreadystatechange内で値を戻そうとしてうまくいかなかったお話

javascript
スポンサーリンク

 

JSっても女子〇学生のことじゃないです。いかがわしくないです。

 

スポンサーリンク

目的

ポケモン努力値調整ツールの開発をだらだらと続けているのですが、API側は実装できたのでようやくフロント側に着手している状態です。

フロント側はC#で開発しようと思っていたのですが、後々のことを考えるとHTML+Jacascriptの知識をつけておいたほうが(仕事にも活きて)良いと思ったので、結果JavascriptでAPIをたたくように実装する必要がありました。

APIはJSONをPOSTするだけ。なので、XMLHttpRequestオブジェクトを利用すればできそう。

というわけで軽く実装してみました。

 

実装

function main(){
    
    //~~~投げるjsonを作る処理(省略)~~~
    json = { (省略) };
    
    resultArr = postJson(json);
    console.log(resultArr);    
}

function postJson(_json){
    xhr = new XMLHttpRequest(); //インスタンス作成
    xhr.open('POST', 'http://dummyhogehoge.com/calc-speed'); //初期化
    xhr.setRequestHeader('content-type', 'application/json'); //リクエストヘッダ設定
    xhr.send(_json); //リクエストを投げる
    
    //readyStateが変更されるたびに実行される関数を格納する変数(オプション)がonreadystatechange
    xhr.onreadystatechange = function() {
        if(xhr.readyState === 4 && xhr.status === 200) {
            result = JSON.parse(xhr.responseText); //戻ってきたjson(String型)を辞書型配列に変換
            return result
        }
        else{
            return null;
        }
    }
}

main();

XMLHttpRequestについての説明が省きます。

私はこのサイトを参考にさせていただきました。

 

順番としては

  1. main関数を実行
  2. 投げるjsonを作成
  3. 作成したjsonをpostJson関数に食わせる
  4. POSTする
  5. POSTした結果を返す
  6. コンソールログに出力

完璧!(多分この時点でJavascript及びXHRを齧ったことのある人は違和感に気づく)

 

んで、以下が実行結果。

null
「絵文字 悩み」の画像検索結果

ん?

 

サーバ側でアクセスログを見ても、ちゃんと200を返している。

ということはresultにちゃんと結果が代入されてくれるはずなのですが・・・

おそらくelse文が悪さしているんだな!というわけでelse文をそのまま消してみる

function main(){
    
    //~~~投げるjsonを作る処理(省略)~~~
    json = { (省略) };
    
    resultArr = postJson(json);
    console.log(resultArr);    
}

function postJson(_json){
    xhr = new XMLHttpRequest(); //インスタンス作成
    xhr.open('POST', 'http://dummyhogehoge.com/calc-speed'); //初期化
    xhr.setRequestHeader('content-type', 'application/json'); //リクエストヘッダ設定
    xhr.send(_json); //リクエストを投げる
    
    //readyStateが変更されるたびに実行される関数を格納する変数(オプション)がonreadystatechange
    xhr.onreadystatechange = function() {
        if(xhr.readyState === 4 && xhr.status === 200) {
            result = JSON.parse(xhr.responseText); //戻ってきたjson(String型)を辞書型配列に変換
            return result
        }
        //消したぜ!
        //else{
        //    return null;
        //}
    }
}

main();
Undefined

ありゃ??

 

Undefindってことは値がうまく戻ってきていない、つまりreturnが実行されていないことが分かります。

つまり、if(xhr.readyState === 4 && xhr.status === 200)のブロックが実行されていない

というわけで、DEBUGを仕込む。

function main(){
    
    //~~~投げるjsonを作る処理(省略)~~~
    json = { (省略) };
    
    resultArr = postJson(json);
    console.log(resultArr);    
}

function postJson(_json){
    xhr = new XMLHttpRequest(); //インスタンス作成
    xhr.open('POST', 'http://dummyhogehoge.com/calc-speed'); //初期化
    xhr.setRequestHeader('content-type', 'application/json'); //リクエストヘッダ設定
    xhr.send(_json); //リクエストを投げる
    
    //readyStateが変更されるたびに実行される関数を格納する変数(オプション)がonreadystatechange
    xhr.onreadystatechange = function() {
        console.log('onreadystatechangeが実行されました'); //DEBUG追加
        if(xhr.readyState === 4 && xhr.status === 200) {
            console.log('IFブロック内の処理が実行されました'); //DEBUG追加
            result = JSON.parse(xhr.responseText); //戻ってきたjson(String型)を辞書型配列に変換
            return result
        }
    }
}

main();

うまくいっていれば出力順は

onreadystatechangeが実行されました
IFブロック内の処理が実行されました
[戻り値]

になるはずです。

というわけで実行。

onreadystatechangeが実行されました 
Undefind
onreadystatechangeが実行されました
onreadystatechangeが実行されました
onreadystatechangeが実行されました
IFブロック内の処理が実行されました

あっ!!!(全てを悟った顔)

 

ここで、私はXMLHttpRequestというのは非同期で実行されるものであることを知りました。

onreadystatechangeに格納した関数はそれ以降独立して動作することになるので、一旦postJson関数は戻り値なしで終わってしまう。

んで、onreadystatechangeにて実行結果が返ってきていざ戻そうとしても、その関数自体すでに完了してしまっているということでしょう。

つまり、onreadystatechangeに格納した関数内で値を戻すという処理は不可能に近いというわけですね。

というより、よく考えたら onreadystatechange に格納した関数内で値を戻そうとしたときってどこに戻そうとするんでしょうか???

呼び出し元はXMLHttpRequestオブジェクトになるので、そこに戻そうとしてなんかおかしなことにならないか?って思ったのですがそれはなんか上手くいくらしい。よくわからん。

 

つまり、この問題は非同期実行(≒マルチスレッド実行)だったので失敗していたということでしょう。

もうちょい調べてみると、XMLHttpRequestには同期実行できるモードもあるみたいです。

使い方は簡単!open()メソッドの第3引数にfalseを入れるだけ!

function main(){
    
    //~~~投げるjsonを作る処理(省略)~~~
    json = { (省略) };
    
    resultArr = postJson(json);
    console.log(resultArr);    
}

function postJson(_json){
    xhr = new XMLHttpRequest(); //インスタンス作成
    xhr.open('POST', 'http://dummyhogehoge.com/calc-speed', false); //初期化
    xhr.setRequestHeader('content-type', 'application/json'); //リクエストヘッダ設定
    xhr.send(_json); //リクエストを投げる
    
    //readyStateが変更されるたびに実行される関数を格納する変数(オプション)がonreadystatechange
    xhr.onreadystatechange = function() {
        console.log('onreadystatechangeが実行されました'); //DEBUG追加
        if(xhr.readyState === 4 && xhr.status === 200) {
            console.log('IFブロック内の処理が実行されました'); //DEBUG追加
            result = JSON.parse(xhr.responseText); //戻ってきたjson(String型)を辞書型配列に変換
            return result
        }
    }
}

main();
onreadystatechangeが実行されました
IFブロック内の処理が実行されました
[戻り値] 

これでよい気がします。

が、XHRの同期実行は最近は推奨されておらず、ブラウザによっては実行自体エラーにしてしまうものもあるとのこと。

GoogleChromeはアラートが出力される程度で実行自体はできたのですが、この後のバージョンでどうなるのか全く保証がありません。

その状況を鑑みると、XHRの同期実行は導入しないほうが吉でしょう。

 

結論

じゃあ、どうするのかというと。

function main(){
    
    //~~~投げるjsonを作る処理(省略)~~~
    json = { (省略) };
    
    postJson(json);   
}

function postJson(_json){
    xhr = new XMLHttpRequest(); //インスタンス作成
    xhr.open('POST', 'http://dummyhogehoge.com/calc-speed', false); //初期化
    xhr.setRequestHeader('content-type', 'application/json'); //リクエストヘッダ設定
    xhr.send(_json); //リクエストを投げる
    
    //readyStateが変更されるたびに実行される関数を格納する変数(オプション)がonreadystatechange
    xhr.onreadystatechange = function() {
        if(xhr.readyState === 4 && xhr.status === 200) {
            result = JSON.parse(xhr.responseText); //戻ってきたjson(String型)を辞書型配列に変換
            console.log(result);
        }
    }
}

main();

戻さない!

これに尽きる。というか一番わかりよいですね。

 

ちなみに、同期実行するとサーバ側から値が戻ってくるまでそのページがフリーズします。

正しくはなにも入力を受け付けなくなる、という感じですが。

そういった状態になるので、排他される状況なのでしょうね。

 

まとめ

  • XHRはデフォルトが非同期実行。
  • 同期実行は推奨されていないので使用しないほうが吉
  • XHRを使用した関数内では値は戻さないように実装するのが良いと思う。

sleepとかで値が戻ってくるまで待つ、という手もあると思いますが、Javascriptってデフォルトでsleep関数ないんですね・・・

コメント

タイトルとURLをコピーしました