JSONPを誰かに説明してもイマイチわかってくれないことがたまにあるのでまとめてみます

まず 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にします

とりあえず 文字列なので文字列にしてみましょう
JavaScriptでは改行が文字列中に書けないので
"1,2,3,4,5"
"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"
変数がいっぱいあるのは扱いづらいのでまとめます
a = "1,2,3,4,5"+"\n"+
"a,b,c,d,e"+"\n"+
"v,w,x,y,z"
これでこのファイルをscriptタグで読みこめば変数aにデータが入ってます
カンペキです!

非同期処理なんです

ですが このデータが入ったファイルを静的に(ページ開いた時に)読み込むならいいんですが 動的に(クリックした時とかに)読み込むなら いつ変数aにデータが入るのかわかりません

scriptタグでの読み込みは非同期になるので
document.head.appendChild(script_element);
fn(a);
みたいにscriptタグをセット直後に変数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);
うわー なんともめんどくさいですね
しかも もし読み込みが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);
これで 外部ファイルを読み込むことが出来ました

と ここまでは私がやっていたことなのですが 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)} を実行したのと同じように動きます
先に関数を用意しておいて 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などの見出しを考えると変なものになる傾向があるようです