docs: function (options, callbackId, progressCallbackId) {
      const DEFAULT_METHOD = 'GET'
      const DEFAULT_MODE = 'cors'
      const DEFAULT_TYPE = 'text'
  
      const methodOptions = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH']
      const modeOptions = ['cors', 'no-cors', 'same-origin', 'navigate']
      const typeOptions = ['text', 'json', 'jsonp', 'arraybuffer']
  
      // const fallback = false  // fallback from 'fetch' API to XHR.
      const sender = this.sender
  
      const config = utils.extend({}, options)
  
      // validate options.method
      if (typeof config.method === 'undefined') {
        config.method = DEFAULT_METHOD
        console.warn('[h5-render] options.method for \'fetch\' API has been set to '
          + 'default value \'' + config.method + '\'')
      }
      else if (methodOptions.indexOf((config.method + '')
          .toUpperCase()) === -1) {
        return console.error('[h5-render] options.method \''
          + config.method
          + '\' for \'fetch\' API should be one of '
          + methodOptions + '.')
      }
  
      // validate options.url
      if (!config.url) {
        return console.error('[h5-render] options.url should be set for \'fetch\' API.')
      }
  
      // validate body content for method 'GET'.
      if (config.method.toUpperCase() === 'GET') {
        let body = config.body
        if (utils.isPlainObject(body)) {
          body = qs.stringify(body)
        }
        let url = config.url
        let hashIdx = url.indexOf('#')
        hashIdx <= -1 && (hashIdx = url.length)
        let hash = url.substr(hashIdx)
        hash && (hash = '#' + hash)
        url = url.substring(0, hashIdx)
        url += (config.url.indexOf('?') <= -1 ? '?' : '&') + body + hash
        config.url = url
      }
  
      // validate options.mode
      if (typeof config.mode === 'undefined') {
        config.mode = DEFAULT_MODE
      }
      else if (modeOptions.indexOf((config.mode + '').toLowerCase()) === -1) {
        return console.error('[h5-render] options.mode \''
          + config.mode
          + '\' for \'fetch\' API should be one of '
          + modeOptions + '.')
      }
  
      // validate options.type
      if (typeof config.type === 'undefined') {
        config.type = DEFAULT_TYPE
        console.warn('[h5-render] options.type for \'fetch\' API has been set to '
          + 'default value \'' + config.type + '\'.')
      }
      else if (typeOptions.indexOf((config.type + '').toLowerCase()) === -1) {
        return console.error('[h5-render] options.type \''
            + config.type
            + '\' for \'fetch\' API should be one of '
            + typeOptions + '.')
      }
  
      // validate options.headers
      config.headers = config.headers || {}
      if (!utils.isPlainObject(config.headers)) {
        return console.error('[h5-render] options.headers should be a plain object')
      }
  
      // validate options.timeout
      config.timeout = parseInt(config.timeout, 10) || 2500
  
      const _callArgs = [config, function (res) {
        sender.performCallback(callbackId, res)
      }]
      if (progressCallbackId) {
        _callArgs.push(function (res) {
          // Set 'keepAlive' to true for sending continuous callbacks
          sender.performCallback(progressCallbackId, res, true)
        })
      }
  
      if (config.type === 'jsonp') {
        _jsonp.apply(this, _callArgs)
      }
      else {
        _xhr.apply(this, _callArgs)
      }
    }