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'
)
]
今のところちゃんと動いてるけど、テスト書いてないので何かあったらどうしよう((((;゚Д゚))))