code: function asyncMemoize(fn, context, hasher) {
      var caches = callProxy.cache = {},
          callbackLists = {}
  
      if (typeof context === "function") {
          hasher = context
          context = null
      }
  
      if (typeof hasher === "undefined") {
          hasher = defaultHasher
      }
  
      return callProxy
  
      function callProxy() {
          var args = [].slice.call(arguments),
              cb = args.pop(),
              key = hasher.apply(null, args)
  
          if (caches[key]) {
              return typeof cb === "function" && cb.apply(null, caches[key])
          } else if (callbackLists[key]) {
              return callbackLists[key].push(cb)
          }
  
          callbackLists[key] = [cb]
  
          args.push(callbackProxy)
  
          fn.apply(context, args)
  
          function callbackProxy() {
              caches[key] = arguments
              var list = callbackLists[key]
              delete callbackLists[key]
              // it might undefined >_< if the callback is blocking
              list && list.forEach(function (cb) {
                  typeof cb === "function" && cb.apply(this, caches[key])
              }, this)
          }
      }
  }
docs: /*
  memoizes asynchronous functions. The asynchronous function must have
  a callback as a last argument, and that callback must be called.
  
  Memoization means that the function you pass in will only be called once
  for every different type of argument. If the async function only
  has a callback argument then it will only be called once. The
  results of invocation are cached
  
  @param Function fn - function to memoize
  @param Object context - optional context for the function
  @param Function hasher - optional custom hasher function. This will
  be called on the arguments of the memoized function. The result
  of the hasher will be the key the cached data will be stored under.
  
  @return Function - the memoized function