JSONP
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 1
JSONPを誰かに説明してもイマイチわかってくれないことがたまにあるのでまとめてみます
まず JSONPは新しい仕組みとか関数やクラスじゃなくて今までにあるものだけでできています
ちょっとした発想というか裏ワザですね
考えれば思いつく人は思いつけるものです
JSONPを知る前に私も似たものは作っていたのですがもう一歩足りてなかった感じです
そういうと XHRなどAjaxを思いつくと思います
ですがXHRはドメインが違うところに接続できません
そういうときにどうするかですが 同じドメインにサーバサイドプログラムおいて サーバが目的のファイルにアクセスしてその結果を返すようにする そういうことができれば簡単です
ブラウザとしては同じドメインのサーバにアクセスしてるだけで目的のデータがもらえるということです
ですが 毎回 自サーバ通すのは無駄ですし そういうプログラムが設置できないことのほうが多いです
そうです scriptタグです
「非同期でJavaScriptでXMLとかを通信するもの」なのでAjax=XHRではないのです
iframeも一応Ajaxとして扱えますが最近はほぼ見ないですよね
しかも最近は こっちも ドメインが違うと表示はできるのですが frame内のオブジェクトへのアクセスをJavaScriptからできなくされています
つまり外部からデータをJavaScriptから使える形で取得できません
で scriptタグですが scriptタグってjQueryなどでgoogleなど外部のサーバに置いてるのを持ってきてますよね
ですがscriptタグの中身はJavaScriptを書いてるのが前提なので それ以外のテキストファイルやjsonファイルだとパースエラーで終わってしまいます
パースエラーでも中身をとれればいいのですが HTMLファイルに書いたJavaScriptと違ってscriptタグのinnerHTMLではデータを取ってこれません
(この仕様やめてほしいんですよね imgタグで持ってきた画像ファイルのバイナリデータをJavaScriptで処理とかしたいので)
欲しいデータがエクセルで作ったcsv形式としましょう
とりあえず 文字列なので文字列にしてみましょう
JavaScriptでは改行が文字列中に書けないので
これだと エラーは出なくなりますが 文字列3つを評価しただけで終わりです
データはどこにも残らないので意味が無いです
なら変数に入れましょう
カンペキです!
scriptタグでの読み込みは非同期になるので
読み込みまで待つならwhileでループと思う人がいるかもしれません
しかし JavaScriptの非同期処理はシングルスレッドでやっているので 非同期処理に回されたものは メインの処理が終わってしまってから実行されます
なのでforとかwhileでループしてる間は scriptタグを読み込み終わっていても中身はいつまで待っても実行されません
そこで JavaScript版 sleepとも言われるsetTimeoutで100msごとに入っているかの確認をします
setTimeoutではwhileなどと違ってその時間が経ってから実行する用に登録しておいて一度メインの処理は終わらせるので その間に非同期に回された処理が実行できます
しかも もし読み込みが201msで終わったとしてもチェックが100ms置きなので99ms無駄に待つことになります
かといって チェック間隔を縮めるとチェック回数がすごく多くなってしまいます
ということで scriptタグの onload イベントに読み込み後の処理を行う関数をセットします
と ここまでは私がやっていたことなのですが JSONPはこれではないです
もう一歩進んだやり方です
そこをもっと短縮するには「scriptタグ内で関数を呼ぶ」という方法です
そうすれば一端 グローバル変数に入れる必要がなくなり イベント処理も必要無くなります
グローバルを汚さずに済むのは結構うれしいですよね
読み込むファイルをこういうふうにしておきます
先に関数を用意しておいて scriptタグを読み込むと勝手にその関数を実行してくれるというです
読み込むファイルを工夫して テキストじゃなくてJavaScriptのオブジェクトと形式で書いておくと楽になります
これがJSONPです
上の例だとJSONになってないですが JSONPという名前なので 実際はJSON型になってるのがほとんどです
(一応配列だけもJSONと呼べたような気もしますがJSONぽくないです)
といっても この仕組みとしては実は別にどんな型でもいいんですよね
ただ JSON固定のほうが使う側が楽なのでJSONPで返すサーバたてるならJSONにしといたほうがいいと思います
今回の例ではfnだったところをサーバ側で書き換えてくれます
自分で作った関数名に合わせてくれるってことです
もちろんですがURLは適当なのでアクセスしてもたぶんエラーです
あまりないと思うのですが サーバ側で関数名を決めているところもあります
その場合は受けとる側で 指定された関数名で受け取ったデータの処理をする関数を定義しておかないといけません
server.js
今回はserver.jsが固定データを返すファイルですが 実際はPHPなどのサーバサイド言語でクエリに応じた検索をデータベースからした結果だったり クエリで指定されたファイルの中身だったりかと思います
サービス作る側ならぜひ callback=fn などでJSONを実行する関数名を決めれるようにして欲しいです
どうでもいいことですが本文書ききってからh1などの見出しを考えると変なものになる傾向があるようです
まず JSONPは新しい仕組みとか関数やクラスじゃなくて今までにあるものだけでできています
ちょっとした発想というか裏ワザですね
考えれば思いつく人は思いつけるものです
JSONPを知る前に私も似たものは作っていたのですがもう一歩足りてなかった感じです
なにそれ?
JavaScriptで外部のデータを取得するものですそういうと XHRなどAjaxを思いつくと思います
ですがXHRはドメインが違うところに接続できません
そういうときにどうするかですが 同じドメインにサーバサイドプログラムおいて サーバが目的のファイルにアクセスしてその結果を返すようにする そういうことができれば簡単です
ブラウザとしては同じドメインのサーバにアクセスしてるだけで目的のデータがもらえるということです
ですが 毎回 自サーバ通すのは無駄ですし そういうプログラムが設置できないことのほうが多いです
JSONPを作ろう
じゃあどうするの?ということですが JavaScriptとというかHTMLには外部のサーバのものでも読み込むことができるものがありますそうです scriptタグです
XHRだと思った?ざ~んねん、scriptタグだよ!
もうひとつのAjaxですね「非同期でJavaScriptでXMLとかを通信するもの」なのでAjax=XHRではないのです
iframeも一応Ajaxとして扱えますが最近はほぼ見ないですよね
しかも最近は こっちも ドメインが違うと表示はできるのですが frame内のオブジェクトへのアクセスをJavaScriptからできなくされています
つまり外部からデータをJavaScriptから使える形で取得できません
で scriptタグですが scriptタグってjQueryなどでgoogleなど外部のサーバに置いてるのを持ってきてますよね
ですがscriptタグの中身はJavaScriptを書いてるのが前提なので それ以外のテキストファイルやjsonファイルだとパースエラーで終わってしまいます
パースエラーでも中身をとれればいいのですが HTMLファイルに書いたJavaScriptと違ってscriptタグのinnerHTMLではデータを取ってこれません
(この仕様やめてほしいんですよね imgタグで持ってきた画像ファイルのバイナリデータをJavaScriptで処理とかしたいので)
JavaScriptじゃないならJavaScriptにしてしまえばいいじゃない
またも じゃあどうするの? ですが JavaScriptじゃないからエラーでるんならJavaScript形式にしてしまえばいいじゃん と考えましょう欲しいデータがエクセルで作ったcsv形式としましょう
1,2,3,4,5
a,b,c,d,e
v,w,x,y,z
このデータをJavaScriptにしますa,b,c,d,e
v,w,x,y,z
とりあえず 文字列なので文字列にしてみましょう
JavaScriptでは改行が文字列中に書けないので
"1,2,3,4,5"
"a,b,c,d,e"
"v,w,x,y,z"
にします"a,b,c,d,e"
"v,w,x,y,z"
これだと エラーは出なくなりますが 文字列3つを評価しただけで終わりです
データはどこにも残らないので意味が無いです
なら変数に入れましょう
a = "1,2,3,4,5"
b = "a,b,c,d,e"
c = "v,w,x,y,z"
変数がいっぱいあるのは扱いづらいのでまとめますb = "a,b,c,d,e"
c = "v,w,x,y,z"
a = "1,2,3,4,5"+"\n"+
"a,b,c,d,e"+"\n"+
"v,w,x,y,z"
これでこのファイルをscriptタグで読みこめば変数aにデータが入ってます"a,b,c,d,e"+"\n"+
"v,w,x,y,z"
カンペキです!
非同期処理なんです
ですが このデータが入ったファイルを静的に(ページ開いた時に)読み込むならいいんですが 動的に(クリックした時とかに)読み込むなら いつ変数aにデータが入るのかわかりませんscriptタグでの読み込みは非同期になるので
document.head.appendChild(script_element);
fn(a);
みたいにscriptタグをセット直後に変数aを参照してもまだセットできてないってエラーですfn(a);
読み込みまで待つならwhileでループと思う人がいるかもしれません
しかし JavaScriptの非同期処理はシングルスレッドでやっているので 非同期処理に回されたものは メインの処理が終わってしまってから実行されます
なのでforとかwhileでループしてる間は scriptタグを読み込み終わっていても中身はいつまで待っても実行されません
そこで JavaScript版 sleepとも言われるsetTimeoutで100msごとに入っているかの確認をします
setTimeoutではwhileなどと違ってその時間が経ってから実行する用に登録しておいて一度メインの処理は終わらせるので その間に非同期に回された処理が実行できます
setTimeout(function check_a(){
if(typeof a !== "undefined")
return fn(a);
else
setTimeout( check_a,100);
},100);
うわー なんともめんどくさいですねif(typeof a !== "undefined")
return fn(a);
else
setTimeout( check_a,100);
},100);
しかも もし読み込みが201msで終わったとしてもチェックが100ms置きなので99ms無駄に待つことになります
かといって チェック間隔を縮めるとチェック回数がすごく多くなってしまいます
イベントを使おう
JavaScriptにある程度慣れてれば「読み込み終わったら」というイベント時に実行する関数を設定しておけば 何秒ごとにチェックなんていらないと思いますよねということで scriptタグの onload イベントに読み込み後の処理を行う関数をセットします
var script_element = document.createElement("script");
script_element.src = "abc.js";
script_elem.onload = function(){
fn(a)
}
document.head.appendChild(script_element);
これで 外部ファイルを読み込むことが出来ましたscript_element.src = "abc.js";
script_elem.onload = function(){
fn(a)
}
document.head.appendChild(script_element);
と ここまでは私がやっていたことなのですが JSONPはこれではないです
もう一歩進んだやり方です
ここからが真のJSONPだ
さっきの方法では scriptタグで呼んだコード内では変数をセットするだけで 読み込み終了イベントが起きたらscriptタグ内で設定された変数を引数にして関数を実行しますそこをもっと短縮するには「scriptタグ内で関数を呼ぶ」という方法です
そうすれば一端 グローバル変数に入れる必要がなくなり イベント処理も必要無くなります
グローバルを汚さずに済むのは結構うれしいですよね
読み込むファイルをこういうふうにしておきます
fn("1,2,3,4,5"+"\n"+
"a,b,c,d,e"+"\n"+
"v,w,x,y,z");
そうすればさっきの onload の時のfunction(){fn(a)} を実行したのと同じように動きます"a,b,c,d,e"+"\n"+
"v,w,x,y,z");
先に関数を用意しておいて scriptタグを読み込むと勝手にその関数を実行してくれるというです
読み込むファイルを工夫して テキストじゃなくてJavaScriptのオブジェクトと形式で書いておくと楽になります
fn([[1,2,3,4,5],["a","b","c","d","e"],["v","w","x","y","z"]]);
これがJSONPです
上の例だとJSONになってないですが JSONPという名前なので 実際はJSON型になってるのがほとんどです
(一応配列だけもJSONと呼べたような気もしますがJSONぽくないです)
といっても この仕組みとしては実は別にどんな型でもいいんですよね
ただ JSON固定のほうが使う側が楽なのでJSONPで返すサーバたてるならJSONにしといたほうがいいと思います
実際に使ってみよう
サーバ側に合わせてやる必要なんて無い
JSONPはよくWebAPIでも使われていて JSONPに対応してると URL部分のクエリにcallbackという指定ができると思いますhttp://abcd.jp/getdata?method=json&query=aaaa&callback=fn
このcallbackにはデータを実行する関数名を書いておきます今回の例ではfnだったところをサーバ側で書き換えてくれます
自分で作った関数名に合わせてくれるってことです
もちろんですがURLは適当なのでアクセスしてもたぶんエラーです
あまりないと思うのですが サーバ側で関数名を決めているところもあります
その場合は受けとる側で 指定された関数名で受け取ったデータの処理をする関数を定義しておかないといけません
サンプルさん、こっちです
上の方のサンプルがすごく部分的で全体がわかりにくいと思うので動く形なものを載せておきますserver.js
fn([
{
name: "JSONPますたー",
skill: "JSONPunch",
},{
name: "JSONPまいすたー",
skill: "JSONPoision",
}
]);
client.html
<!doctype html>
<script>
var fn = function(arg){ // 受け取ったデータが引数に入ってこの関数が実行される
document.getElementById("area").innerHTML =
arg[0].name+"のスキルは"+arg[0].skill+"です<br />"+
arg[1].name+"のスキルは"+arg[1].skill+"です";
}
var elem = document.createElement("script");
elem.src = "server.js";
document.head.appendChild(elem);
</script>
<div id="area">読み込み後ココに表示</div>
今回はserver.jsが固定データを返すファイルですが 実際はPHPなどのサーバサイド言語でクエリに応じた検索をデータベースからした結果だったり クエリで指定されたファイルの中身だったりかと思います
サービス作る側ならぜひ callback=fn などでJSONを実行する関数名を決めれるようにして欲しいです
どうでもいいことですが本文書ききってからh1などの見出しを考えると変なものになる傾向があるようです