ES6 - 有条件地在Promise中发出重试信号,直到达到最大重试次数

时间:2018-03-06 18:11:25

标签: javascript ecmascript-6 promise

我已经看到了一些关于重试Promise的问题,但是我要做的事情略有不同,因为我想管理重试/拒绝承诺有条件地直到达到最大重试次数。

举一个简单的例子,假设我们在XMLHttpRequest周围包裹一个承诺。当请求加载状态为...

  • 200:解决承诺
  • 299:立即重试
  • 399:立即拒绝
  • 499:从服务器获取内容,然后重试

请注意,此处有一个范围可以在重试之前执行异步行为。

我一直在研究的解决方案涉及两个Promise。

  • 第一个是围绕每次尝试的包装器,并根据该尝试的结果进行简单的解决/拒绝。
  • 第二个是围绕 set 尝试的包装器,它可以有条件地处理各个Promise 的拒绝。

回到我提到的例子......

  • 第一个Promise管理每个XmlHttpRequest,解析状态200并拒绝其他。
  • 当任何尝试得到解决时,第二个Promise会自行解决。每当尝试被拒绝时,它将根据该尝试的状态代码决定下一个操作(重试,拒绝,获取然后重试等)。

我认为我正朝着正确的方向前进,但似乎无法找到具体的解决方案。我正在寻找为这种有条件的重试承诺创建通用包装器。'

修改

这是一个正在进行的解决方案:

async function tryAtMost(maxAttempts, asyncCall, handleError)
{
    for (let i = 0; i < maxAttempts; i++)
    {
        try 
        { 
            return await asyncCall(); 
        }
        catch (error)
        {
            const nextAction = await handleError(error); // await some async request (if available) before proceeding
            const actionError = new Error(nextAction.error);

            switch (nextAction.type)
            {
                case ACTIONS.ABORT:
                    throw actionError;
                case ACTIONS.RETRY:
                    if (i === maxAttempts - 1) { throw actionError; }
                    else { continue; }
            }
        }
    }
}

3 个答案:

答案 0 :(得分:2)

有几种方法可以做到这一点,正如另一篇文章所示。我个人认为不必使用类。我会使用类似

之类的东西来接近它
async function fetchWithRetries(theURL, remainingRetries = 5) {
  const response = await fetch(theURL);

  switch (response.status) {
    case 200:
      return await response.json(); // or whatever you need
    case 299:
      if (remainingRetries === 0) {
        throw new Error();
      }
      return await fetchWithRetries(theURL, remainingRetries - 1);
    case 399:
      throw new Error();
    case 499:
      if (remainingRetries === 0) {
        throw new Error();
      }

      const otherData = await fetchOtherData();

      return await fetchWithRetries(theURL, remainingRetries - 1);

    default:
      // TODO: You didn't specify other codes?
  }
}

答案 1 :(得分:1)

我只想创建一个返回async函数的类(返回Promise)。

  • Class实例会跟踪attempts
  • async函数尝试获取x次的内容,等于maxAttempts的数量。
  • 如果请求正确响应而没有任何错误,只需返回结果。
  • 否则请继续尝试,直至耗尽maxAttempts
  • 的数量

使用request-promise-native的Node.js示例:

const rp = require('request-promise-native')

class RetryableFetch {
  constructor({ url, maxAttempts = 3 }) {
    this.url = url
    this.maxAttempts = maxAttempts    
    this.attempts = 0

    return this.generateRequest()
  }

  async generateRequest() {
    for (let i = 0; i < this.maxAttempts; i++) {
      try {
        return await rp(this.url)
      } catch(err) {
        switch (err.statusCode) {
          // Add more cases here as you see fit.
          case 399:
            throw err
            break;
          default:
            if (++this.attempts === this.maxAttempts) throw err
        }
      }
    }
  }
}

用法:

new RetryableFetch({
  url: 'https://www.google.com'
})
.then(result => {
  console.log(result)
})
.catch(err => {
  console.error(err)
})

如果您希望在浏览器中使用rp,则可以将<body> <form action="physical_server_check_list_saved.php" method="post"> <table width="800" align="left"> <th width="800" align="left" colspan="2" bgcolor="#D3D3D3">SERVER INFORMATION</th> <tr> <td width="200" align="left">Server name:</td> <td width="600" align="left"><input type="text" size="100" name="servername"></td /tr> tr> <td width="200" align="left">Contact:</td> <td width="600" align="left"><input type="text" size="100" name="contact"></td /tr> <tr> <td width="800" align="left" colspan="2"> <input type="submit" value="submit"> </td> </tr> </table> </form> </body> 替换为<tr> <td width="200" align="left">Server name:</td> <td width="600" align="left"> <?php $servername = $_POST['servername']; echo $servername; ?> </td> </tr> <tr> <td width="200" align="left">Contact:</td>> <td width="600" align="left"> <?php $contact = $_POST['contact']; echo $contact; ?> </td> </tr> ,因为它们都使用基于Promise的API。

答案 2 :(得分:1)

基于您的评论:

  

我正在寻找为这种“有条件”重试承诺创建通用包装器。

这是一个更通用的包装器:

  • 它允许您指定最大尝试次数。
  • 您传递了自己的Promise
  • 您在施工现场指定如果承诺拒绝并且尚未达到最大尝试将会发生什么。

// Class Retryable

class Retryable {
  constructor({ promise, maxAttempts = 1, attemptRetry }) {
    this.promise = promise
    this.maxAttempts = maxAttempts
    this.attemptRetry = attemptRetry

    this.attempts = 0
  }

  generateTry() {
    console.info('generating request')

    return this.promise().catch(err => {
      if (++this.attempts === this.maxAttempts) throw err

      return this.attemptRetry(err, () => this.generateTry() , () => {
        throw err
      })
    })
  }
}

// Usage

const retryable = new Retryable({
  maxAttempts: 4,
  promise: () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject({ status: 500 })
        // If you `resolve` here instead you will trigger `.then()`
      }, 200)
    })
  },

  attemptRetry: function(err, yes, no) {
    switch (err.status) {
      case 500:
        return yes()
        break;
      default:
        return no()
    }
  }
})

retryable.generateTry().then(result => {
  console.log(result)
}).catch(err => {
  console.error(err)
})