function Request(url, options, f, retryConfig) {
  // ('url')
  if(_.isString(url)){
    // ('url', f)
    if(_.isFunction(options)){
      f = options;
    }

    if(!_.isObject(options)){
      options = {};
    }

    // ('url', {object})
    options.url = url;
  }

  if(_.isObject(url)){
    if(_.isFunction(options)){
      f = options;
    }
    options = url;
  }

  this.maxAttempts = retryConfig.maxAttempts;
  this.retryDelay = retryConfig.retryDelay;
  this.fullResponse = retryConfig.fullResponse;
  this.attempts = 0;

  /**
   * Option object
   * @type {Object}
   */
  this.options = sanitizeHeaders(options);

  /**
   * Return true if the request should be retried
   * @type {Function} (err, response, body, options) -> [Boolean, Object (optional)]
   */
  this.retryStrategy = _.isFunction(options.retryStrategy) ? options.retryStrategy : RetryStrategies.HTTPOrNetworkError;

  /**
   * Return a number representing how long request-retry should wait before trying again the request
   * @type {Boolean} (err, response, body) -> Number
   */
  this.delayStrategy = _.isFunction(options.delayStrategy) ? options.delayStrategy : function() { return this.retryDelay; };

  this._timeout = null;
  this._req = null;

  this._callback = _.isFunction(f) ? _.once(f) : null;

  // create the promise only when no callback was provided
  if (!this._callback) {
    this._promise = makePromise(this, retryConfig.promiseFactory);
  }

  this.reply = function requestRetryReply(err, response, body) {
    if (this._callback) {
      return this._callback(err, response, body);
    }

    if (err) {
      return this._reject(err);
    }

    // resolve with the full response or just the body
    response = this.fullResponse ? response : body;
    this._resolve(response);
  };
}