domのreadyで、ajaxでトークンが有効か確認して、有効ならajaxで一覧データが取得したい
あると思います。
※長いので、本題から読んで頂いて問題ないです。
何も考えずに書く、こんな処理になるかと。
$(document).ready(function(){
$.ajax(
{url:'./renew_token.php',
data:{
token:window.localStorage?localStorage.getItem('token'):""
}
}
).then(function(auth_response){
:
$.ajax({
:
}).then(function(some_response){
:
})
}).catch(function(err){
:
});
});
これ、非常に嫌いです。
読むのもつらければ、修正するのもつらい。
一般的には、これは関数に切り出すべきだといわれます。
function auth(auth,callback){
:
callback(xxx);
return ;
}
function get_list(list_conditions,callback){
:
callback(list_data);
}
一見、すっきりしたように見えて好ましいですが、果たして本当でしょうか?
こんな感じで使う?
auth({xxxxxx},getlist);
まぁ今の状態ではだいぶすっきりしてますが、もし、認証に失敗したときになんらかの処理をしたい、と仕様変更があったらくそめんどくさいです。
最初から考えとけよ、という話にもなりますが、そもそも、リストを取得するのと、認証するのは別問題であって、認証に成功しなくても特定のリストは表示したいとか、もうあとから嫌な事を言われるのが目に見えてます。
本題
そこで、こういうのを使ってます。
emitter = function create_emittor(){
var e = {};
e.jq = $({});
e.on = function(event,callback){
e.jq.on(event,function(x,arg){
callback(arg);
});
}
e.trigger = function(event,args){
e.jq.trigger(event,args);
}
e.proxy = function(event_success,event_error){
if(!event_error) event_error = event_success;
return function(err,result){
if(!err) setTimeout(function(){e.trigger(event_success,result)});
else setTimeout(function(){e.trigger(event_error,err)});
}
};
e.proxyone = function(event){
return function(result){
setTimeout(function(){e.trigger(event,result)});
}
}
return e;
}
var $e = emitter();
※余談にも書きましたが、これを書いたときに手元にjQueryがあったから利用しているだけで、on,triggerがあれば何でもいいです。
使い方
var $e = emitter();
$(document).ready($e.proxyone('renew_token'));
$e.on('renew_token',function(){
$.ajax({
url:'./renew_token.php',
data:{
token:window.localStorage?localStorage.getItem('token'):""
},
dataType:"json"
})
.then($e.proxyone('token_refreshed'))
.catch($e.proxyone('token_rejected'));
});
//リフレッシュされたら、リストを取得
$e.on('token_refreshed',$e.proxyone('get_list'));
$e.on('token_rejected' ,$e.proxyone('redirect_to_error'));
$e.on('get_list',function(token){
//トークンで、リストを取得
alert(token);
var list = {};
//なんかリスト取得処理
list.list = ['a','b','c'];
$e.trigger('refresh_list',list);
});
$e.on('refresh_list',function(list){
//もらったデータを処理
for(var idx in list.list){
alert(""+idx+":"+list.list[idx]);
}
})
また、indexedDBのような非同期なDBを使用するときも、以下のような雰囲気でできます。
//サンプル関数:一度目は失敗し、二度目は成功する。
var count = 0;
function some_err_result_function(callback){
if(count++==0) {
callback('error');
return;
}
callback(null,'ready');
}
$(document).ready($e.proxyone('init_xxxdb'));
var _db = null;
$e.on('init_xxxdb',function(){
//DB初期化
some_err_result_function($e.proxy('xxxdb_ready','xxxdb_error'));
});
$e.on('xxxdb_ready',function(db){
//準備OK
_db = db;
alert('db_ready');
})
$e.on('xxxdb_error',function(){
alert('db_error');
setTimeout($e.proxyone('init_xxxdb'),500);//500ms後にもう一回チャレンジ
})
と、割と柔軟に再試行、再描画が行えます。
また、イベントのため、トークンがリフレッシュされたら、localStorageにも保存したい、なんて当たり前だけど忘れてた仕様の追加が起こっても、既存のロジックと完全に切り離して追加実装できます。
$e.on('token_refreshed',function(token){
if(window.localStorage) window.localStorage.setItem('token',token);
});
単純にリスナを追加するだけですね。
また、サンプルにあるように、未実装のイベント(redirect_to_error)があっても特に死ぬことはないので、あとから後始末追加したりも割と簡単です。
ちょっと良いおまけ
webworker化したり、serviceworker化するのが楽です。
余談
on,triggerだけを行うために、jQueryを使いたくない場合は、適当に実装したバージョンもあります。
e._handlers = {};
e.on = function(event,eventHandler,eventHandlerName){
if(!event) throw 'EventName Required';
if(!eventHandler) throw 'eventHandler Required';
if(!e._handlers[event]) e._handlers[event] = [];
if(eventHandlerName) eventHandler._eventHandlerName = eventHandlerName;
e._handlers[event].push(eventHandler);
};
e.off = function(event,eventHandler,eventHandlerName){
if(e._handlers[event]){
for(var k in e._handlers[event]){
if(!e._handlers[event].hasOwnProperty(k)) continue;
if(eventHandler && e._handlers[event][k]==eventHandler){
delete e._handlers[event][k];
}else if(eventHandlerName && e._handlers[event][k]["_eventHandlerName"]==eventHandlerName){
delete e._handlers[event][k];
}
}
}
};
e.once = function(event,eventHandler){
var handlerName = date().now + Math.random();
var _this = this;
e.on(event,function(){
e.off(event,null,handlerName);
return eventHandler.apply(t,arguments);
},handlerName);
};
e.trigger = function(event,...args){
if(!e._handlers[event]) return;
for(var k in e._handlers[event]) if(e._handlers[event].hasOwnProperty(k)) setTimeout(function(){
e._handlers[event][k].apply(e._handlers[event][k],args);
},0);
};
//proxy以降は同一です。
こっちのコードで動いている実績はビミョーですが、動かしてみたところなんとなくは動いているようです。
バブリングさせたかったら、jQueryバージョンのほうがお手軽ですね。
追記
敏感に反応してしまってすみませんが、Promise案件というTweet拝見しました。
ちょっと言葉足らずだったかな、と言い訳補足しておきます。
Promiseで解決した結果を、結果に意味を持たせて疎に次の処理へ続けたい、という意図があります。
(わざわざjQueryのthenとcatchを分解しているところで伝わると思っていましたが、言葉足らずでしたね。)