2013-10-25 37 views
13

我得到的数据我的控制器内像这样的服务重试:AngularJS承诺时被拒绝从一个异步服务

myApp.controller('myController', ['$scope', 'AsyncService', 
function($scope, AsyncService) { 
    $scope.getData = function(query) { 
     return AsyncService.query(query).then(function(response) { 
      // Got success response, return promise 
      return response; 
     }, function(reason) { 
      // Got error, query again in one second 
      // ??? 
     }); 
    } 
}]); 

我的问题:

  1. 如何再次查询服务,当我得到服务中出现错误而未返回承诺。
  2. 在我的服务中这样做会更好吗?

谢谢!

+0

是,重新连接重新在服务中,这样,在控制器中,您可以简单地获得已解析的数据。 – tymeJV

+0

定义您的功能并将其命名。在拒绝中打电话给它。那简单! – Mohsen

+0

试图在控制器中返回$ scope.getData(query),但不再发送承诺 – Angelin

回答

18

您可以在服务本身而不是控制器中重试请求。

所以,AsyncService.query可以是这样的:

AsyncService.query = function() { 
    var counter = 0 
    var queryResults = $q.defer() 

    function doQuery() { 
    $http({method: 'GET', url: 'https://example.com'}) 
     .success(function(body) { 
     queryResults.resolve(body) 
     }) 
     .error(function() { 
     if (counter < 3) { 
      doQuery() 
      counter++ 
     } 
     }) 
    } 

    return queryResults.promise 
} 

而且你可以在控制器中摆脱你的误差函数:

myApp.controller('myController', ['$scope', 'AsyncService', 
    function($scope, AsyncService) { 
    $scope.getData = function(query) { 
     return AsyncService.query(query).then(function(response) { 
     // Got success response 
     return response; 
     }); 
    } 
    } 
]); 
+1

可能希望有一个计数器,如果服务器关闭,这只会反复循环。 – tymeJV

+0

@tymeJV更新与计数器。 –

+1

非常感谢你的例子,它的作用就像一个魅力;) – Angelin

7

这其实工作原理:

angular.module('retry_request', ['ng']) 
    .factory('RetryRequest', ['$http', '$q', function($http, $q) { 
    return function(path) { 
     var MAX_REQUESTS = 3, 
      counter = 1, 
      results = $q.defer(); 

     var request = function() { 
     $http({method: 'GET', url: path}) 
      .success(function(response) { 
      results.resolve(response) 
      }) 
      .error(function() { 
      if (counter < MAX_REQUESTS) { 
       request(); 
       counter++; 
      } else { 
       results.reject("Could not load after multiple tries"); 
      } 
      }); 
     }; 

     request(); 

     return results.promise; 
    } 
    }]); 

那么只是使用它的一个例子:

RetryRequest('/api/token').then(function(token) { 
    // ... do something 
}); 

你必须声明你的模块时,需要它:

angular.module('App', ['retry_request']); 

而且在你的控制器:

app.controller('Controller', function($scope, RetryRequest) { 
    ... 
}); 

如果有人想用某种补偿或随机的时间来改善它重试请求,那会更好。我希望有一天类似的东西将在Angular Core

+0

在.error,它可能是很好添加:else {results.reject(path +'在'+ MAX_REQUESTS +'重试后失败。'); } –

+0

也许''results.reject(...)'正确处理错误 – Dorian

+0

所以我试了一下,它工作正常(3次错误后拒绝) – Dorian

2

我写了一个带指数回退的实现,它不使用递归(这会创建嵌套的堆栈帧,是否正确?)它的实现方式有使用多个定时器,它总是为make_single_xhr_call创建所有堆栈帧(即使在成功之后,而不是在失败之后)。我不确定它是否值得(特别是如果平均情况是成功的话),但这是值得思考的。

我很担心调用之间的竞争条件,但是如果javascript是单线程的并且没有上下文切换(这会允许一个$ http.success被另一个中断并允许它执行两次)重新在这里,对不对?

此外,我很新的angularjs和现代的JavaScript,所以约定可能有点肮脏也。让我知道你的想法。

var app = angular.module("angular", []); 

app.controller("Controller", ["$scope", "$http", "$timeout", 
    function($scope, $http, $timeout) { 

    /** 
    * Tries to make XmlHttpRequest call a few times with exponential backoff. 
    * 
    * The way this works is by setting a timeout for all the possible calls 
    * to make_single_xhr_call instantly (because $http is asynchronous) and 
    * make_single_xhr_call checks the global state ($scope.xhr_completed) to 
    * make sure another request was not already successful. 
    * 
    * With sleeptime = 0, inc = 1000, the calls will be performed around: 
    * t = 0 
    * t = 1000 (+1 second) 
    * t = 3000 (+2 seconds) 
    * t = 7000 (+4 seconds) 
    * t = 15000 (+8 seconds) 
    */ 
    $scope.repeatedly_xhr_call_until_success = function() { 
    var url = "/url/to/data"; 
    $scope.xhr_completed = false 
    var sleeptime = 0; 
    var inc = 1000; 
    for (var i = 0, n = 5 ; i < n ; ++i) { 
     $timeout(function() {$scope.make_single_xhr_call(url);}, sleeptime); 
     sleeptime += inc; 
     inc = (inc << 1); // multiply inc by 2 
    } 
    }; 

    /** 
    * Try to make a single XmlHttpRequest and do something with the data. 
    */ 
    $scope.make_single_xhr_call = function(url) { 
    console.log("Making XHR Request to " + url); 

    // avoid making the call if it has already been successful 
    if ($scope.xhr_completed) return; 
    $http.get(url) 
     .success(function(data, status, headers) { 
     // this would be later (after the server responded)-- maybe another 
     // one of the calls has already completed. 
     if ($scope.xhr_completed) return; 
     $scope.xhr_completed = true; 
     console.log("XHR was successful"); 
     // do something with XHR data 
     }) 
     .error(function(data, status, headers) { 
     console.log("XHR failed."); 
     }); 
    }; 

}]); 
1

我落得这样做了很多,所以我写了一个库,以帮助解决这个问题:)

https://www.npmjs.com/package/reattempt-promise-function

在这个例子中,你可以做类似

myApp.controller('myController', ['$scope', 'AsyncService', 
function($scope, AsyncService) { 
    var dogsQuery = { family: canine }; 
    $scope.online = true; 
    $scope.getDogs = function() { 
     return reattempt(AsyncService.query(dogsQuery)).then(function(dogs) { 
      $scope.online = true; 
      $scope.dogs = dogs; 
     }).catch(function() { 
      $scope.online = false; 
     }); 
    } 
}]); 
2

关注此文章Promises in AngularJS, Explained as a Cartoon

您需要t当响应来自下5XX类

我已经写了一个服务叫做HTTP,可以通过将所有HTTP CONFIGS作为

var params = { 
    method: 'GET', 
    url: URL, 
    data: data 
} 

被称为然后调用服务方法如下O仅重试:

<yourDefinedAngularFactory>.http(params, function(err, response) {}); 

http: function(config, callback) { 
 
    function request() { 
 
    var counter = 0; 
 
    var queryResults = $q.defer(); 
 

 
    function doQuery(config) { 
 
     $http(config).success(function(response) { 
 
     queryResults.resolve(response); 
 
     }).error(function(response) { 
 
     if (response && response.status >= 500 && counter < 3) { 
 
      counter++; 
 
      console.log('retrying .....' + counter); 
 
      setTimeout(function() { 
 
      doQuery(config); 
 
      }, 3000 * counter); 
 
     } else { 
 
      queryResults.reject(response); 
 
     } 
 
     }); 
 
    } 
 
    doQuery(config); 
 
    return queryResults.promise; 
 
    } 
 
    request(config).then(function(response) { 
 
    if (response) { 
 
     callback(response.errors, response.data); 
 
    } else { 
 
     callback({}, {}); 
 
    } 
 
    }, function(response) { 
 
    if (response) { 
 
     callback(response.errors, response.data); 
 
    } else { 
 
     callback({}, {}); 
 
    } 
 
    }); 
 
}

+1

5XX错误的好选择。当error.status等于-1(超时,无连接,...)时,也可以重试。 – VinceOPS