async function awsRequest(service, method, ...args) {
  // Checks regarding expectations on service object
  if (isObject(service)) {
    ensureString(service.name, { name: 'service.name' })
  } else {
    ensureString(service, { name: 'service' })
    service = { name: service }
  }
  const BASE_BACKOFF = 5000
  const persistentRequest = async (f, numTry = 0) => {
    try {
      return await f()
    } catch (e) {
      const { providerError } = e
      if (
        numTry < MAX_RETRIES &&
        providerError &&
        ((providerError.retryable &&
          providerError.statusCode !== 403 &&
          providerError.code !== 'CredentialsError' &&
          providerError.code !== 'ExpiredTokenException') ||
          providerError.statusCode === 429)
      ) {
        const nextTryNum = numTry + 1
        const jitter = Math.random() * 3000 - 1000
        // backoff is between 4 and 7 seconds
        const backOff = BASE_BACKOFF + jitter
        log.info(
          [
            `Recoverable error occurred (${
              e.message
            }), sleeping for ~${Math.round(backOff / 1000)} seconds.`,
            `Try ${nextTryNum} of ${MAX_RETRIES}`,
          ].join(' '),
        )
        await wait(backOff)
        return persistentRequest(f, nextTryNum)
      }
      throw e
    }
  }
  const request = await requestQueue.add(() =>
    persistentRequest(async () => {
      const requestId = ++requestCounter
      const awsService = getServiceInstance(service, method)
      awsLog.debug(`request: #${requestId} ${service.name}.${method}`, args)
      const req = awsService[method](...args)
      try {
        const result = await req.promise()
        awsLog.debug(
          `request result: #${requestId} ${service.name}.${method}`,
          result,
        )
        return result
      } catch (err) {
        awsLog.debug(
          `request error: #${requestId} - ${service.name}.${method}`,
          err,
        )
        let message = err.message != null ? err.message : String(err.code)
        if (message.startsWith('Missing credentials in config')) {
          // Credentials error
          // If failed at last resort (EC2 Metadata check) expose a meaningful error
          // with link to AWS documentation
          // Otherwise, it's likely that user relied on some AWS creds, which appeared not correct
          // therefore expose an AWS message directly
          let bottomError = err
          while (
            bottomError.originalError &&
            !bottomError.message.startsWith('EC2 Metadata')
          ) {
            bottomError = bottomError.originalError
          }

          const errorMessage = bottomError.message.startsWith('EC2 Metadata')
            ? [
                'AWS provider credentials not found.',
                ' Learn how to set up AWS provider credentials',
                ` in our docs here: http://slss.io/aws-creds-setup`,
              ].join('')
            : bottomError.message
          message = errorMessage
          // We do not want to trigger the retry mechanism for credential errors
          throw Object.assign(
            new ServerlessError(errorMessage, 'AWS_CREDENTIALS_NOT_FOUND'),
            {
              providerError: Object.assign({}, err, { retryable: false }),
            },
          )
        }
        const providerErrorCodeExtension = (() => {
          if (!err.code) return 'ERROR'
          if (typeof err.code === 'number') return `HTTP_${err.code}_ERROR`
          return normalizeErrorCodePostfix(err.code)
        })()
        if (err.stack) {
          log.debug(`${err.stack}\n${'-'.repeat(100)}`)
        }
        throw Object.assign(
          new ServerlessError(
            message,
            `AWS_${normalizeErrorCodePostfix(
              service.name,
            )}_${normalizeErrorCodePostfix(
              method,
            )}_${providerErrorCodeExtension}`,
          ),
          {
            providerError: err,
            providerErrorCodeExtension,
          },
        )
      }
    }),
  )
  return request
}
Execute request to AWS service
@param {Object|string} [service] - Description of the service to call
@prop [service.name] - Name of the service to call, support subclasses
@prop [service.params] - Parameters to apply when creating the service and doing the request
@prop [service.params.credentials] - AWS Credentials to use
@prop [service.params.useCache ] - Wether to reuse result of the same request cached locally
@prop [service.params.region] - Region in which the call should be made (default to us-east-1)
@prop [service.params.isS3TransferAccelerationEnabled] - Use s3 acceleration when available for the request
@param {String} method - Method to call
@param {Array} args - Argument for the method call