Angular.jsでDeferredしたい
jQueryを使わずにAngular.jsだけで実装したいけど、
うまいことDeferredできないかなぁ、と思って。
Serviceに$qっていうのがあった
A promise/deferred implementation inspired by Kris Kowal's Q.
こんな感じで使えるっぽい。
myDeferredA = (state) ->
deferred = $q.defer()
successMsg = 'Success!'
errorMsg = 'Error!'
setTimeout( ->
$scope.$apply ->
if state == 0
deferred.resolve successMsg
else
deferred.reject errorMsg
, 3000)
deferred.promise
promiseA = myDeferredA(Math.random()*2|0)
promiseA.then (msg) ->
console.log "success: #{msg}"
, (msg) ->
console.log "error: #{msg}"
非同期処理の場合Viewの更新が必要になるので、
$scope.$applyの中でdeferred.resolve/rejectする。
jQuery.Deferredみたいに.done()/.fail()は無いので、
.then(successCallback, errorCallback)で成功/失敗を分ける。
$q.whenと$q.allについては再度検証中
CSS3 animationを簡単にチェーンできるやつを作ってみる
Deferred を使おう:CSSトランジション終了の検知を参考にしながら、よしなに実装してみる。
理想はこんな感じ
animation = cssAnimation.create element
animation
.end(->
...
).end(->
...
).end
あらかじめ、$qと$windowをDIしておく
class Animation
constructor: (elem) ->
css = $window.getComputedStyle elem[0], ''
duration = css[supportAnimation.duration] || '0s'
@elem = elem
@_msec = +duration * 1000
@_queue = []
@_isProgress = false
@_promise = null
このへんはほぼ参考サイトのまんま。
promiseに.then()を追加していく(ような)形にしたかったので、プロパティとして用意しておく。
# msecとか結局使ってなかった…
# eventをbindしてdeferオブジェクトを返す
set: (event, callback, isReturnPromise) ->
isReturnPromise = false unless isReturnPromise?
_dfdFunc = () =>
dfd = $q.defer()
@elem.bind event, =>
@elem.unbind event, callback
# callbackの中でresolve/rejectできるようにしておく
callback.call(@, dfd)
dfd.resolve() unless isReturnPromise
return dfd.promise
@done _dfdFunc
return @
# animationEnd用のsetラッパー
end: (callback, isReturnPromise) ->
return @set supportAnimation.end, callback, isReturnPromise
setメソッドでdeferredオブジェクトを作りつつ、@elemにeventをバインドしていく。
callbackの中でsetTimeoutとかすることも踏まえて(と言うか実際やりたかった)、callbackにdeferredオブジェクトを渡しておく。
(callbackの引数でdfdを拾ってあげる)
さらに、setの第3引数(endの場合第2引数)でフラグを受け取ってresolve()するかしないかも分けておく。
最後にdoneメソッドにpromiseを渡して、thisを返しておく。
後々animationEndだけじゃなくなることも考えて、
endメソッドはsetメソッドのラッパーにしておく。
supportAnimationはbootstrap-transition.jsから拝借
# はじめはdurationも使う予定だった
supportAnimation = do ->
el = document.createElement 'tada'
transEndEventNames =-
'WebkitAnimation' : 'webkitAnimationEnd'
'animation' : 'animationend'
transPropNames =-
'WebkitAnimation' : 'webkitAnimationDuration'
'animation' : 'animationDuration'
for name of transEndEventNames
if el.style[name] isnt undefined
return {
end : transEndEventNames[name],
duration : transPropNames[name]
}
doneメソッドはanimationEnd以外の非同期処理でチェーンする場合に使えるインターフェースとして。
setの中からも使うようにした。
# promiseのsuccessCallbackに`callback`を追加する
# promiseが無ければ`callback`を実行して、promiseを作る
done: (callback) ->
if @_promise == null
@_promise = callback.call(@)
else
@_promise = @_promise.then(callback)
@_promiseを.then()の返り値で上書きすることで.then()のチェーンっぽくなった(…かな?)
最後にcreateメソッドを返しておしまい。
return {
create: (elem) ->
new Animation(elem)
}
こんな感じで書けるようになった
module.controller 'hoge', ['cssAnimation', (cssAnimation) ->
element = angular.element document.querySelector '.animation'
animation = cssAnimation.create element
animation
.end(->
console.log '1st animation is ended'
).end(->
console.log '2nd animation is ended'
).end((dfd) ->
$timeout(->
console.log '3rd animation is ended'
dfd.resolve()
, 3000)
, true).done(->
console.log 'all animations are ended'
)
]
今のところちゃんと動いてるけど、テスト書いてないので何かあったらどうしよう((((;゚Д゚))))