Angular.jsでDeferredしたくて頑張ってみたらcss animationが綺麗に書けた


## Angular.jsでDeferredしたい

jQueryを使わずにAngular.jsだけで実装したいけど、
うまいことDeferredできないかなぁ、と思って。

## Serviceに$qっていうのがあった
http://docs.angularjs.org/api/ng.$q

 >A promise/deferred implementation inspired by Kris Kowal's Q.

こんな感じで使えるっぽい。

```coffeescript
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)`で成功/失敗を分ける。

<del>
さらに、`$q.when`を使えば並列処理もできる。
</del>

<del>
`$q.all`を使うと、複数のpromiseを一つにまとめ、
そのどれか一つでも`resolve`/`reject`すれば処理を完了する。
\# 今ひとつ挙動がつかめてなくてちょっと理解度が足りない
</del>

$q.whenと$q.allについては再度検証中

## CSS3 animationを簡単にチェーンできるやつを作ってみる

[Deferred を使おう：CSSトランジション終了の検知](http://level0.kayac.com/#!2012/08/detecting_css_transition_end.php)を参考にしながら、よしなに実装してみる。

理想はこんな感じ

```coffeescript
animation = cssAnimation.create element
animation
	.end(->
		...
	).end(->
		...
	).end
```

あらかじめ、$qと$windowをDIしておく

```coffeescript
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とか結局使ってなかった…

```coffeescript
# 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](https://github.com/twitter/bootstrap/blob/master/docs/assets/js/bootstrap-transition.js)から拝借
\# はじめは`duration`も使う予定だった

```coffeescript
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`の中からも使うようにした。

```coffeescript
# promiseのsuccessCallbackに`callback`を追加する
# promiseが無ければ`callback`を実行して、promiseを作る
done: (callback) ->
	if @_promise == null
		@_promise = callback.call(@)
	else
		@_promise = @_promise.then(callback)
```
`@_promise`を`.then()`の返り値で上書きすることで`.then()`のチェーンっぽくなった(…かな？)

最後に`create`メソッドを返しておしまい。

```coffeescript
return {
	create: (elem) ->
		new Animation(elem)
}
```

こんな感じで書けるようになった

```coffeescript
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'
		)
]
```

今のところちゃんと動いてるけど、テスト書いてないので何かあったらどうしよう((((；ﾟДﾟ))))