docs: function define () {
    var args = Array.prototype.slice.call(arguments)
    var i, l
  
    var name = args.shift()
    if (is.array(name)) {
      name = args.shift()
      for (i = 0, l = name.length; i < l; i++) {
        define.apply(this, [name[i]].concat(args))
      }
      return this
    }
  
    if (!is.string(name)) throw new Error('Method name is not defined')
  
    var invoke = is.function(args[args.length - 1]) ? wrapFn(args.pop()) : null
    var pre = args.map(wrapFn)
  
    // define scope method
    this[name] = function () {
      var args = Array.prototype.slice.call(arguments)
      var self = this
      var callback
  
      if (is.function(args[args.length - 1])) callback = args.pop()
  
      // init pipeline
      var pipe = []
      var obj = this
  
      // prototype chain
      while (obj) {
        if (obj.hasOwnProperty('_hooks') && obj._hooks[name]) {
          pipe.unshift(obj._hooks[name])
        }
        obj = Object.getPrototypeOf(obj)
      }
      // pre middlewares
      pipe.unshift(pre)
  
      // invoke middleware
      if (invoke) pipe.push(invoke)
  
      pipe = flatten(pipe)
  
      // context object and next triggerer
      var ctx = new EventEmitter()
      ctx.method = name
      ctx.args = args
      var index = 0
      var size = pipe.length
  
      function next (err, res) {
        if (err || index === size) {
          var args = Array.prototype.slice.call(arguments)
          // callback when err or end of pipeline
          if (callback) callback.apply(self, args)
          args.unshift('end')
          ctx.emit.apply(ctx, args)
        } else if (index < size) {
          var fn = pipe[index]
          index++
          var val = fn.call(self, ctx, next)
          if (is.promise(val)) {
            val.then(function (res) {
              next(null, res)
            }, function (err) {
              next(err || new Error())
            })
          } else if (fn.length < 2) {
            // args without next() & not promise, sync func
            next(null, val)
          }
        }
      }
  
      if (callback) {
        next()
      } else {
        // use promise if no callback
        return new Promise(function (resolve, reject) {
          callback = function (err, result) {
            if (err) return reject(err)
            resolve(result)
          }
          next()
        })
      }
    }
    return this
  }