function fetch(...args:any):Promise {

  // create task ID for receiving progress event
  let taskId = getUUID()
  let options = this || {}
  let subscription, subscriptionUpload, stateEvent, partEvent
  let respInfo = {}
  let [method, url, headers, body] = [...args]

  // # 241 normalize null or undefined headers, in case nil or null string
  // pass to native context
  headers = _.reduce(headers, (result, value, key) => {
    result[key] = value || ''
    return result
  }, {});

  // fetch from file system
  if(URIUtil.isFileURI(url)) {
    return fetchFile(options, method, url, headers, body)
  }

  // from remote HTTP(S)
  let promise = new Promise((resolve, reject) => {
    let nativeMethodName = Array.isArray(body) ? 'fetchBlobForm' : 'fetchBlob'

    // on progress event listener
    subscription = emitter.addListener('RNFetchBlobProgress', (e) => {
      if(e.taskId === taskId && promise.onProgress) {
        promise.onProgress(e.written, e.total, e.chunk)
      }
    })

    subscriptionUpload = emitter.addListener('RNFetchBlobProgress-upload', (e) => {
      if(e.taskId === taskId && promise.onUploadProgress) {
        promise.onUploadProgress(e.written, e.total)
      }
    })

    stateEvent = emitter.addListener('RNFetchBlobState', (e) => {
      if(e.taskId === taskId)
        respInfo = e
      promise.onStateChange && promise.onStateChange(e)
    })

    subscription = emitter.addListener('RNFetchBlobExpire', (e) => {
      if(e.taskId === taskId && promise.onExpire) {
        promise.onExpire(e)
      }
    })

    partEvent = emitter.addListener('RNFetchBlobServerPush', (e) => {
      if(e.taskId === taskId && promise.onPartData) {
        promise.onPartData(e.chunk)
      }
    })

    // When the request body comes from Blob polyfill, we should use special its ref
    // as the request body
    if( body instanceof Blob && body.isRNFetchBlobPolyfill) {
      body = body.getRNFetchBlobRef()
    }

    let req = RNFetchBlob[nativeMethodName]

    /**
     * Send request via native module, the response callback accepts three arguments
     * @callback
     * @param err {any} Error message or object, when the request success, this
     *                  parameter should be `null`.
     * @param rawType { 'utf8' | 'base64' | 'path'} RNFB request will be stored
     *                  as UTF8 string, BASE64 string, or a file path reference
     *                  in JS context, and this parameter indicates which one
     *                  dose the response data presents.
     * @param data {string} Response data or its reference.
     */
    req(options, taskId, method, url, headers || {}, body, (err, rawType, data) => {

      // task done, remove event listeners
      subscription.remove()
      subscriptionUpload.remove()
      stateEvent.remove()
      partEvent.remove()
      delete promise['progress']
      delete promise['uploadProgress']
      delete promise['stateChange']
      delete promise['part']
      delete promise['cancel']
      // delete promise['expire']
      promise.cancel = () => {}

      if(err)
        reject(new Error(err, respInfo))
      else {
        // response data is saved to storage, create a session for it
        if(options.path || options.fileCache || options.addAndroidDownloads
          || options.key || options.auto && respInfo.respType === 'blob') {
          if(options.session)
            session(options.session).add(data)
        }
        respInfo.rnfbEncode = rawType
        resolve(new FetchBlobResponse(taskId, respInfo, data))
      }

    })

  })

  // extend Promise object, add `progress`, `uploadProgress`, and `cancel`
  // method for register progress event handler and cancel request.
  // Add second parameter for performance purpose #140
  // When there's only one argument pass to this method, use default `interval`
  // and `count`, otherwise use the given on.
  // TODO : code refactor, move `uploadProgress` and `progress` to StatefulPromise
  promise.progress = (...args) => {
    let interval = 250
    let count = -1
    let fn = () => {}
    if(args.length === 2) {
      interval = args[0].interval || interval
      count = args[0].count || count
      fn = args[1]
    }
    else {
      fn = args[0]
    }
    promise.onProgress = fn
    RNFetchBlob.enableProgressReport(taskId, interval, count)
    return promise
  }
  promise.uploadProgress = (...args) => {
    let interval = 250
    let count = -1
    let fn = () => {}
    if(args.length === 2) {
      interval = args[0].interval || interval
      count = args[0].count || count
      fn = args[1]
    }
    else {
      fn = args[0]
    }
    promise.onUploadProgress = fn
    RNFetchBlob.enableUploadProgressReport(taskId, interval, count)
    return promise
  }
  promise.part = (fn) => {
    promise.onPartData = fn
    return promise
  }
  promise.stateChange = (fn) => {
    promise.onStateChange = fn
    return promise
  }
  promise.expire = (fn) => {
    promise.onExpire = fn
    return promise
  }
  promise.cancel = (fn) => {
    fn = fn || function(){}
    subscription.remove()
    subscriptionUpload.remove()
    stateEvent.remove()
    RNFetchBlob.cancelRequest(taskId, fn)
  }
  promise.taskId = taskId

  return promise

}
Create a HTTP request by settings, the `this` context is a `RNFetchBlobConfig` object.
@param  {string} method HTTP method, should be `GET`, `POST`, `PUT`, `DELETE`
@param  {string} url Request target url string.
@param  {object} headers HTTP request headers.
@param  {string} body
        Request body, can be either a BASE64 encoded data string,
        or a file path with prefix `RNFetchBlob-file://` (can be changed)
@return {Promise}
        This promise instance also contains a Customized method `progress`for
        register progress event handler.