angular-cached-resource
Version:
An AngularJS module to interact with RESTful resources, even when browser is offline
134 lines (112 loc) • 5.29 kB
text/coffeescript
CACHE_RETRY_TIMEOUT = 60000 # one minute
module.exports = (providerParams, $q) ->
{$log} = providerParams
ResourceCacheEntry = require('./resource_cache_entry')(providerParams)
Cache = require('./cache')(providerParams)
# this could be a lot nicer with ES6 WeakMaps
# (http://www.nczonline.net/blog/2014/01/21/private-instance-members-with-weakmaps-in-javascript/)
# but till then this is to maintain private instance members
flushQueueDeferreds = {}
resetDeferred = (queue) ->
deferred = $q.defer()
flushQueueDeferreds[queue.key] = deferred
queue.promise = deferred.promise
deferred
resolveDeferred = (queue) ->
flushQueueDeferreds[queue.key].resolve()
class ResourceWriteQueue
logStatusOfRequest: (status, action, params, data) ->
$log.debug("#{action} for #{@key} #{angular.toJson(params)} #{status} (queue length: #{@queue.length})", data)
constructor: (@CachedResource, @$timeout) ->
@key = "#{@CachedResource.$key}/write"
@queue = Cache.getItem(@key, [])
write.busy = false for write in @queue
resetDeferred(@)
if @queue.length is 0
resolveDeferred(@) # initialize the queue with a resolved promise
enqueue: (params, resourceData, action, deferred) ->
resetDeferred(@) if @queue.length is 0
resourceParams = if angular.isArray(resourceData)
resourceData.map((resource) -> resource.$params())
else
resourceData.$params()
write = @findWrite {params, action}
if not write?
@queue.push {params, resourceParams, action, deferred}
@_update()
else
write.deferred?.promise.then (response) ->
deferred.resolve response
write.deferred?.promise.catch (error) ->
deferred.reject error
@logStatusOfRequest('enqueued', action, params, resourceData)
findWrite: ({action, params}) ->
for write in @queue
return write if action is write.action and angular.equals(params, write.params)
removeWrite: ({action, params}) ->
newQueue = []
for entry in @queue
newQueue.push entry unless action is entry.action and angular.equals(params, entry.params)
@queue = newQueue
if @queue.length is 0 and @timeoutPromise
@$timeout.cancel @timeoutPromise
delete @timeoutPromise
@_update()
resolveDeferred @ if @queue.length is 0
flush: (done) ->
@promise.then done if angular.isFunction(done)
@_setFlushTimeout()
@_processWrite(write) for write in @queue
processResource: (params, done) ->
notDone = true
for write in @_writesForResource(params)
@_processWrite write, =>
if notDone and @_writesForResource(params).length is 0
notDone = false
done()
_writesForResource: (params) ->
# TODO FIX FIX FIXME this should compare against individual write.resourceParams, which could be a nested array
write for write in @queue when angular.equals(params, write.params)
_processWrite: (write, done) ->
return if write.busy
write.busy = true
if angular.isArray(write.resourceParams)
cacheEntries = write.resourceParams.map (resourceParams) =>
new ResourceCacheEntry(@CachedResource.$key, resourceParams).load()
writeData = cacheEntries.map (cacheEntry) -> cacheEntry.value
else
cacheEntries = [new ResourceCacheEntry(@CachedResource.$key, write.resourceParams).load()]
writeData = cacheEntries[0].value
onSuccess = (value) =>
@removeWrite write
write.deferred?.resolve value
@logStatusOfRequest('succeeded', write.action, write.resourceParams, writeData)
done() if angular.isFunction(done)
onFailure = (error) =>
if error and error.status >= 400 and error.status < 500
@removeWrite write
$log.error "#{write.action} to #{@CachedResource.$key} failed with error #{error.status}",
{ method: error.config.method, url: error.config.url, writeData }
else
write.busy = false
@logStatusOfRequest("failed with error #{angular.toJson error}; still in queue", write.action, write.resourceParams, writeData)
write.deferred?.reject error
@CachedResource.$resource[write.action](write.params, writeData, onSuccess, onFailure).$promise.then (savedResources) =>
savedResources = if angular.isArray(savedResources) then savedResources else [savedResources]
for resource in savedResources
resourceInstance = new @CachedResource(resource)
cacheEntry = new ResourceCacheEntry(@CachedResource.$key, resourceInstance.$params()).load()
cacheEntry.set(resource, false)
@logStatusOfRequest('processed', write.action, write.resourceParams, writeData)
_setFlushTimeout: ->
if @queue.length > 0 and not @timeoutPromise
@timeoutPromise = @$timeout angular.bind(@, @flush), CACHE_RETRY_TIMEOUT
@timeoutPromise.then =>
delete @timeoutPromise
@_setFlushTimeout()
_update: ->
savableQueue = @queue.map (write) ->
params: write.params
resourceParams: write.resourceParams
action: write.action
Cache.setItem @key, savableQueue