投稿日:2017/08/17
更新日:2017/08/18
非同期通信
ここではjQueryを使った非同期通信について述べる。
ちなみに、非同期処理のためだけにjQueryを使う人がいたくらいスタンダードな存在だったが
今は非同期専用ライブラリであるaxiosも人気がある。
ReactやVue.jsのようなview特化コンポーネントと組み合わせて使われることが多い。
ではAngularはどうかというと、こちらはフルスタックなので専用APIを持っている。
また、サーバーサイド(node.js)ではrequestモジュールの独占市場と言っても過言ではない。
非同期通信の基礎
http://www.hoge.jp
のHTMLを取得するなら、以下のように書く。
$.ajax({
url: 'http://www.hoge.jp',
dataType: 'html'
}).done(function(html){
// 引数:htmlにプレーンなテキストとしてHTMLがわたる
});
非同期なので、以下の内容の場合処理の順番は大体このようになる。
(console.logの番号通りの順番)
console.log(1);
$.ajax({
url: 'http://www.hoge.jp',
dataType: 'html'
}).done(function(html){
// 引数:htmlにプレーンなテキストとしてHTMLがわたる
console.log(3);
});
console.log(2);
コールバック地獄とは
上記で分かる通り、HTML取得後それをゴニョゴニョするような場合は
doneメソッドの中に処理を記述する必要がある。
では、その処理の成果物をもって別アドレスにPOST送信する場合はどうなるだろうか。
答えは以下の通り。
$.ajax({
url: 'http://www.hoge.jp',
dataType: 'html'
}).done(function(html){
$.ajax({
method: 'post',
url: 'http://www.hoge.jp/form',
data: { sample: html },
dataType: 'html'
}).done(function(html){
// POST送信後の処理
});
});
非同期をすればするほど、ネストが深くなるのが確認できる。
これを繰り返すのがコールバック地獄と呼ばれるものだ。
処理が複雑になればなるほど、ネストが深くどこかで関数化して逃がす必要が出てくる。
これはES2017のasync/awaitにて解消予定だが、使い方に若干のクセがあるため簡単に、とはいかない。
Promiseの説明の前に
2段階の非同期の処理が終わったら〇〇する関数として、上記を定義してみよう。
function acyncSample(url1, url2, callback){
$.ajax({
url: url1,
dataType: 'html'
}).done(function(html){
$.ajax({
method: 'post',
url: url2,
data: { sample: html },
dataType: 'html'
}).done(function(html){
// 〇〇する
callback(html);
});
});
}
使い方はこう。
let url1 = 'http://www.hoge.jp';
let url2 = 'http://www.hoge.jp/form';
acyncSample(url1, url2, function(html){});
ネストこそ発生するが、定義としてはそれほど悪くないように見える。
では、url以外に可変な情報が必要となり引数が増えたら?エラーハンドリングは?
実装してみよう。
function acyncSample(url1, url2, judge, done, fail){
$.ajax({
url: url1,
dataType: 'html'
}).done(function(html){
$.ajax({
method: 'post',
url: url2,
data: { sample: html, judge: judge },
dataType: 'html'
}).done(function(html){
// 成功した場合〇〇する
done(html);
}).fail(function(){
// 失敗した場合〇〇する
fail('エラーメッセージなど');
});
});
}
引数がみるみる増えていくのが確認できる。
引数の順番にも気を使う必要があるし、メンテナンス性が高いとも言えない。
これは単純な処理の順番の保証でしかない。
Promise
これらを修正し、新しい概念を取り入れたのがPromiseだ。
Promise を使うということは、Promiseオブジェクトを返す ということとほぼ同義である。
Promiseは非同期に関わらず関数に定義することができる。
Promiseオブジェクトのインスタンスをreturnした関数を定義すればよい。
function acyncSample(url1, url2, judge){
return new Promise(function(resolve, reject){
$.ajax({
url: url1,
dataType: 'html'
}).done(function(html){
$.ajax({
method: 'post',
url: url2,
data: { sample: html, judge: judge },
dataType: 'html'
}).done(function(html){
// 成功した場合〇〇する
resolve(html);
}).fail(function(){
// 失敗した場合〇〇する
reject('エラーメッセージなど');
});
});
});
}
上記の使い方はこう。
let url1 = 'http://www.hoge.jp';
let url2 = 'http://www.hoge.jp/form';
acyncSample(url1, url2, true)
.then(function(html){}) // 成功時の処理
.then(function(error){}); // 失敗時の処理
ネストはやはり発生するが、コールバックではなくメソッドにて処理するため記述はスッキリする。
これだけ見るとあまり恩恵は無いように見えるが、真価は処理が複数必要になったときに発揮される。
Promise.all()・・・並列処理
Promiseオブジェクトを返す関数を、配列として引数に渡すことで並列処理をし、全処理が完了後の処理を可能にするメソッド。
処理結果は取得順はなんであれ、引数として渡した配列の順番通りになるため非常にコントロールがしやすい。
上記のacyncSample
関数を、url2のみ変更して並列処理してみる。
let promises = [];
let pages = ['form', 'company', 'menu', 'case'];
let url1 = 'http://www.hoge.jp';
pages.forEach(page=>{
let url2 = url1+'/'+page;
promises.push( acyncSample(url1, url2, true) ); // 関数の配列を作る
});
Promise.all(promises).then(function(htmlArray){
// htmlArrayには取得したhtmlが配列の順番通りに含まれている。
});
4ページ分の通信が発生していることになるが、並列なので処理にかかる時間は最も長い1ページ分でしかないため、パフォーマンスに大きく貢献できる。
直接(逐次)処理
逐次処理ともいうが、Promise.resolve()
にthen
メソッドを繋げていくと逐次処理も実現できるらしい。
これは使ったことがないため、下記を参照する方が早い。
http://qiita.com/toshihirock/items/e49b66f8685a8510bd76
非同期処理を逐次処理で実行する