Promise retries until success/failure with Typescr

2019-07-18 22:59发布

问题:

My mobile app uploads several files to the server in succession, often from remote areas with questionable connection strength. For this reason, I want to make a few attempts to send the file. I also want to move on and attempt the next one in the event of a failure, with all error messages displayed at the end of the export (ie "10 files uploaded, 3 failed...")

However, I'm having trouble figuring out the recursive retry pattern with promises. Here's what I have so far:

sendFile(params, retries = 3){
    console.log("SENDING FILE: ", retries, "attempts remaining", params)

    return new Promise((resolve, reject)=>{
      if(retries > 0){
        this._sendFile(params)
        .then(()=>{
          // Upload Success
          console.log("Upload Success!")
          resolve()
        })
        .catch((err)=>{
          console.log("Upload Fail", err)
          // Retry
          this.sendFile(params, --retries)
        })
      }else{
        console.log("Failed 3 times!!!")
        //Record error
        this.exportStatus.errors.push({
          message:"A file failed to upload after 3 attempts.",
          params: params
        })

        //Resolve and move on to next export
        resolve()

      }
    })

  }

  _sendFile(params){

      // Mobile - File Transfer

        let options = {
          fileKey: "file_transfer",
          fileName: params.fileName,
          httpMethod: "PUT",
          chunkedMode: false,
          headers: {
            "x-user-email":this.settings.user.email,
            "x-user-token":this.settings.user.authentication_token,
          }
        }

        let fileTransfer = this.transfer.create()
        let url = encodeURI(this.settings.api_endpoint + params.url)

        return fileTransfer.upload(params.file, url, options, true)


  }

When I raise an exception on the server, I'll see the "Failed 3 times!!!" error message, but the calling promise does not resolve for the rest of the export to move on. I believe this is because I'm created nested promises (ie creating a new promise with each retry). How can I have the original promise resolve after 3 retries?

Thanks!

回答1:

You could implement a wrapper for Promise() that automatically chains retries for you, allowing you to refactor your code with whatever logic you need and not worry about handling retry logic simultaneously. Your usage could look something like this:

sendFile(params, retries = 3) {
  return Promise.retry(retries, (resolve, reject) => {
    this._sendFile(params).then(resolve, reject)
  })
}

Below is how you could implement Promise.retry():

Object.defineProperty(Promise, 'retry', {
  configurable: true,
  writable: true,
  value: function retry (retries, executor) {
    console.log(`${retries} retries left!`)

    if (typeof retries !== 'number') {
      throw new TypeError('retries is not a number')
    }

    return new Promise(executor).catch(error => retries > 0
      ? Promise.retry(retries - 1, executor)
      : Promise.reject(error)
    )
  }
})

Promise.retry(100, (resolve, reject) => {
  // your sendFile core logic with proper
  // calls to resolve and reject goes here
  const rand = Math.random()

  console.log(rand)

  if (rand < 0.1) resolve(rand)
  else reject(rand)
}).then(
  value => console.log(`resolved: ${value}`),
  error => console.log(`rejected: ${error}`)
)

If you're uncomfortable extending a native object (this would be the correct way to do so, since it is a configurable, non-enumerable and writable property), you can just implement it as a static function:

function retry (retries, executor) {
  console.log(`${retries} retries left!`)

  if (typeof retries !== 'number') {
    throw new TypeError('retries is not a number')
  }

  return new Promise(executor).catch(error => retries > 0
    ? Promise.retry(retries - 1, executor)
    : Promise.reject(error)
  )
}


回答2:

Here's what ended up working:

  sendFile(params, retries = 3, promise = null){
    console.log("SENDING FILE: ", retries, "attempts remaining", params)

    if(retries > 0){
      return this._sendFile(params)
      .then(()=>{
        // Upload Success
        console.log("Upload Success!")
        return Promise.resolve(true)
      })
      .catch((err)=>{
        console.log("Upload Fail", err)

        this.exportStatus.retries++

        return this.sendFile(params, --retries) // <-- The important part
      })
    }else{
      console.log("Failed 3 times!!!")

      this.exportStatus.errors.push({
        message:"A file failed to upload after 3 attempts.",
        params: params
      })

      return Promise.resolve(false)

    }

  }