JSONPを定期実行する
2007.03.27 / javascript
XHRはドメインを越えて通信ができないので、ドメインを越えてAJAXを行うときはXHR以外の方法を取る必要があります。JSONP, iframeなんかの解決法があるかと思うのですが、今回はJSONPの話題をば。
JSONPとはJSONデータをcallbackさせたい関数名のパラメータとして受信し、scriptタグを動的生成することでクロスドメインでAJAX(ここまでくると当然XMLとかもはやどうでもいい)を実現する、、、ということなんですけど、文章で書くとワケわかりません。ので、実行コードを書くとこんな感じ。
http://example.com/data.json?callback=view
と、callbackさせたい関数名をパラメータに付けて(*1)リクエストを投げると
view( { "state" : "good"} );
と、「callback_function( JSON data); 」な、形式で返してくれるものがJSONP。こいつのリクエストをXHRでかけるとドメイン越えでデータを受信できないので、scriptタグでJavaScriptをロードさせる要領でリクエストをかけることで、ドメイン越えしてJSONデータを受信する、、というもの。うーん、これ考えた人すごい。(*2)
で、このJSONPを定期実行させたい、ということもあるかと思います。要するに定期的にx秒毎に別ドメインにアクセスして最新の情報をゲットしたい、なんてとき。そこで微妙にハマったことがあったのでメモ。
単純に考えるとsetIntervalのパラメータにscript要素を追加するような関数を持たせればいいのですが、ポイントはscript要素の追加のさせ方。大きく分けると
・document.writeで要素を書き出し
・DOMを利用してscript要素を追加挿入
の2つになるかな、と思います。
DOMはちょっと苦手なのでdocument.writeの方法をよく使う僕としてはこんな方法を当然のごとく考えました。
function view(){ // JSON処理 }
こんな関数を定義しておいたあとで、次のような関数を定義、実行します。
function loadSessions(){ document.write('<scr' + 'ipt type="text/javascript" src="http://example.com/service?callback=view"> </scr' + 'ipt>'); } loadSessions(); // (1) setInterval("loadSessions()", 5000); // (2)
当然(1)が実行され、サクっと動いたので「よしよし」と思ったら、(2)のsetIntervalで2回目の実行に入ったときに「viewは定義されていません」のエラーが。
うーん?????
これ謎です。setIntervalした後にコールバック関数のviewが上書きされてる or 消されている?ってこと?
Firebugで動的に書き換えられてるHTMLを監視していたのですが、setInterval以降、HTMLのソース表示部分が真っ白になってロックされてしまっているような状態で確認が取れませんでした。 (どなたかこの現象が説明できる方、教えていただけないでしょうか。。)
で、結局、この理由がよく分からないのでなかなか解決できなかったので、document.writeはあきらめてDOM操作の方法をとることに。上のloadSessions関数をこんな感じで再定義。
function loadSessions(){ var readSessionId = 'read'; var api = "http://example.com/service?callback=view"; var head = document.getElementsByTagName('head').item(0); if(document.getElementById(readSessionId)!=null){ head.removeChild(document.getElementById(readSessionId)); } var s = document.createElement('script'); s.setAttribute('type', 'text/javascript'); s.setAttribute('src', api); s.setAttribute('id', readSessionId) s.setAttribute('charset', 'UTF-8'); head.appendChild(s); }
もう見たまんま。scriptタグに適当なIDをつけてappendしています。
ここで、単純にappendするだけでsetIntervalすると、永遠にscript要素が挿入され続けてなんか気持ち悪い…ので、IDを元にscript要素を特定し、その要素を一度削除してから再挿入しています。ここ、applyとかevalとかうまく使うともうちょっと綺麗なコードになるかも?しれません。ここもいい方法あったらぜひ教えていただきたいです。
で、こっちのDOM操作だとsetIntervalもcallbackがちゃんと実行されました。うーんdocument.writeとなんでここまで挙動が違うのか謎。。だけど、とりあえずJSONPのテクとしてメモメモ。
—
(*1) 当然のごとくサービス提供側はよくわからない「関数名っぽいもの」をパラメータに付けられる可能性があるので、内容チェックは必須。Yahoo! では [A-Za-z0-9_.\[\]] に制限されています。うん、納得。
(*2) 「ドメイン越えするAJAX」=「JSONP」という図式がなんとなくいろんなサイトの説明で見たり、このエントリーもそんな書き出しをしているけど、これって本当はちょっとズレてる?はず。GoogleはJSON-in-Scriptなんて名前で呼んでたりするみたいだけど、名前と意味的にはこっちの方が正しいと個人的には思う。