带进度/通知

时间:2015-10-16 23:44:34

标签: angularjs asynchronous promise

我在设置将执行异步http请求的服务时遇到问题。

背景

我有一个后端端点/cars.json当您第一次点击此端点时,将获取将获取所有汽车信息的后台作业。然后,服务器返回状态为200的响应:

{"status":"started", "percentage_completion":0, "data":null, "error":null}

对此终端的连续请求将继续返回状态200并更新 percentage_completion

{"status":"started", "percentage_completion":45, "data":null, "error":null}
{"status":"started", "percentage_completion":90, "data":null, "error":null}

最后,对端点的请求将返回:

{"status":"finished", "percentage_completion":100, "data": {...}, "error":null}

和欢乐日子我有数据,我可以将其添加到$scope

但我很难绕过承诺和有条不紊的做事方式......

我飙了这样的东西:

   var dashboard = angular.module('dashboard', [                                                                                       
      'templates'                                                                                                                     
    ]);                                                                                                                                 

   dashboard.controller('dashboardController', ['carsService', '$scope',                                                          
        function(carsService, $scope){                                                                                             
          $scope.test = "Loading...";                                                                                                   
          carsService.get()                                                                                                        
            .then(function(response){                                                                                                   
              $scope.test = response.data;                                                                                              
            });                                                                                                                         
        }]                                                                                                                              
   );                                                                                                                                  

    dashboard.factory('carsService', ['$http', function($http){                                                                    
      var get = function(){                                                                                                             
        return $http.get('/cars.json');                                                                                            
      };                                                                                                                                
      return {                                                                                                                          
        get: get                                                                                                                        
      };                                                                                                                                
    }]);

它向服务器发送单个请求,并使用表明作业已启动的第一个响应更新范围上的测试。当我几秒钟后刷新页面时,我会使用正确的数据和完成状态更新测试。

什么是最好的方式,角度方式:)与承诺和东西让服务和控制器自动完成。

我头脑中的第一件事是使用$interval或类似的东西,并定期从.then(成功)重新发送请求,但也许有更好的方法。

Angular文档说明了进度/通知承诺,但目前我还不知道如何连接它。

显然我目前还没有完全理解这些承诺,我刚刚介绍了它们 - 你能否分享一些关于如何使用angular来处理异步请求的技巧/资源?

4 个答案:

答案 0 :(得分:4)

我并不认为我完全理解你的问题,但是如果你想要在他们回来时获得进展更新,Angular会在他们的承诺服务中建立一个进度回调(正如你所提到的)。您.then()内的第一个回调是您的成功回调,第二个是您的错误回调,第三个是您的进度回调。

因此,当您将回调链接到承诺时,您可以使用以下语法更新进度指示器:

carsService.get()                                                                                                        
    .then(function(response){
        // success                                                                                                  
        $scope.test = response.data;
        $scope.$apply();  // or wrapped in $timeout                                      
    }, function(error){
        // error
    }, function(percentComplete){
        // progress
        $scope.progress = percentComplete;
        $scope.$apply();  // or wrapped in $timeout
    });

至于在编写和链接你的承诺时使用的最佳语法,John Papa有一个非常棒(并且非常深入)的风格指南,可以帮助你更好地组织你的Angular代码。你可以找到这个here

此处的示例jsfiddle也展示了此通知/进度回调的实际效果。它使用超时来模拟异步调用,但很好地显示了一般的想法。

还要记住,当您进行这些异步调用时,您可能需要运行$scope.$apply()或将回调中的代码包装在$timeout(function(){ //update data });内更新范围数据的回调中,以便运行Angular再次消化周期(异步调用在Angular的内置观察者之外更新您的数据)。这将导致您的视图更新,而无需重新加载页面。有很多关于此问题的博客帖子和SO问题,如果您以前从未遇到过这种行为,可以使用它们。这里的one很好地涵盖了它。

答案 1 :(得分:2)

我猜你可以使用嵌套的promise实现进度通知:

function get(){

    var defer = $q.defer();

    (function fetchData(){

        $http.get('/cars.json').then(function(result){
            if(result.status === 'started'){
                fetchData();
                defer.notify(result);
            }else if(result.status === 'finished'){
                defer.resolve(result);
            }
        }, function(err){
            defer.reject(err);
        })

    })()

    return defer.promise;
}

当您致电get()时,它首先致电fetchData以从服务器请求数据。解决fetchData后,检查result.status,如果状态尚未完成,请再次致电fetchData并通知outter延迟,否则,解决最终结果的延迟

//in controller
carService.get().then(
    function(finalData){ $scope.test = finalData }, 
    function(err){ ... }, 
    function(notifyData){ $scope.test = notifyData }
}

答案 2 :(得分:1)

  

这里有关于AngularJS中$ http服务的参考。

     

https://docs.angularjs.org/api/ng/service/$http

     

这是关于$ http服务的解释以及如何使用then()   功能

select sales.id, sales.refid, reference.refname, sales.invoice,
sales.itemid, sales.price as sellprice, purchase.price as buyprice,
sales.price - purchase.price as profit
from sales
left outer join reference on reference.refid = sales.refid
left outer join purchase on purchase.itemid = sales.itemid

最好的方法是使用HTML5 Server-Sent Events或WebSockets技术,您可以实时获取数据。

在这个演示中,我在then()函数中使用了匿名函数。

我使用PHP,Server Sent Events,JSON和AngularJS Factory服务制作了两个非常简单的演示。

AngularJS工厂服务和PHP JSON响应: cars.php

// Simple GET request example:
$http({
    method: 'GET',
    url: '/someUrl'
}).then(function successCallback(response) {
    // this callback will be called asynchronously
    // when the response is available
}, function errorCallback(response) {
    // called asynchronously if an error occurs
    // or server returns response with an error status.
});

<?php
header("Access-Control-Allow-origin: *"); // To allow cross-origin HTTP request.
header("Content-Type: application/json"); // JSON response.
header("Cache-Control: no-cache"); // No cache.

/*
    Only to display random numeric values.
    In a real scenario the data can be obtained from database.
*/
$min = 0;
$max = 100;
$count = rand($min, $max); // Get a random numeric value.

$status = "started";
if($count == $max)
{
    $status = "finished"; // If the $count == 100 then finished.
}
// Builing an array with current data.
$array = array("status" => $status, "percentage_completion" => $count, "data" => null, "error" => null);
echo json_encode($array); // Encode the array to the json representation.
?>
(function() {
  var dashboard = angular.module("dashboard", []);

  dashboard.controller("dashboardController", ["carsService", "$scope",
    function(carsService, $scope) {
      $scope.statusJSON = "Loading...";
      $scope.testJSON = {};

      $scope.initJSON = function() {
        (function loop() { // Looping function to continous request.
          carsService.get().then(function(response) { // Angular promise by using then() function.
            $scope.testJSON = response.data;
            $scope.statusJSON = $scope.testJSON.status;
            console.log($scope.testJSON);
          }, function(response) {
            console.log("Error: " + response.status);
          });
          setTimeout(loop, 1000); // Call the $http service every 1 second.
        })();
      };
    }
  ]);

  dashboard.factory("carsService", ["$http",
    function($http) {
      return { // This factory service returns an object with the get function.
        get: function() {
          return $http.get("http://dfjb.webcindario.com/cars.php", {
            responseType: "json"
          }); // Returns a $http service.
        }
      };
    }
  ]);
})();
.json {
  border: solid 1px #444444;
  margin: 5px;
  padding: 5px;
}

HTML5服务器发送事件:data.php

<html data-ng-app="dashboard">

<head>
  <title>Demo AngularJS</title>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</head>

<body data-ng-controller="dashboardController">
  <div class="json" data-ng-init="initJSON()">
    <h3>Demo With Angular $http Service</h3>
    <div data-ng-bind="statusJSON"></div>
    percentage_completion: {{testJSON.percentage_completion}}
    <br />
    <progress min="0" max="100" value="{{testJSON.percentage_completion}}" />
  </div>
</body>

</html>

<?php
header("Access-Control-Allow-origin: *"); // To allow cross-origin HTTP request.
header("Content-Type: text/event-stream"); // To send event streams.
header("Cache-Control: no-cache"); // No cache.

/*
    Only to display random numeric values.
    In a real scenario the data can be obtained from database.
*/
$min = 0;
$max = 100;
$count = rand($min, $max); // Get a random numeric value.

$status = "started";
if($count == $max)
{
    $status = "finished"; // If the $count == 100 then finished.
}
// Builing an array with current data.
$array = array("status" => $status, "percentage_completion" => $count, "data" => null, "error" => null);
echo "data: ". json_encode($array) ."\n\n"; // Encode the array to the json representation. The event-stream always start with "data: ".
flush(); // Flush the output data back to the web page.
?>
(function() {
  var dashboard = angular.module("dashboard", []);

  dashboard.controller("dashboardController", ["carsService", "$scope",
    function(carsService, $scope) {
      $scope.statusSSE = "Loading...";
      $scope.testSSE = {};

      $scope.initSSE = function() {
        // Check if EventSource is supported by the browser.
        if (typeof(EventSource) !== "undefined") {
          carsService.getDataSSE().onmessage = function() {
            // The factory service has a function that returns an EventSource object, so this can be accessed in the controller.
            $scope.testSSE = JSON.parse(event.data); // Parse the string in an object.
            $scope.statusSSE = $scope.testSSE.status;
            $scope.$apply(); // Update the $scope variable so can be used in the view.
            console.log($scope.testSSE);
          };
        } else {
          alert("SSE not supported by browser.");
        }
      };
    }
  ]);

  dashboard.factory("carsService", [
    function() {
      return {
        getDataSSE: function() {
          // HTML5 Server-Sent Events Implementation.
          return new EventSource("http://dfjb.webcindario.com/data.php"); // Returns the EventSource object.
        }
      };
    }
  ]);
})();
.sse {
  border: solid 1px #FF44AA;
  margin: 5px;
  padding: 5px;
}

最后,在本演示中,我将向您展示如何使用PHP中的AngularJS Factory服务和使用json响应的HTML5 Server-Sent Events Technology来实现服务器的实时通知。

如果您检查控制台,则会自动在EventStream选项卡中获得一个json字符串。

enter image description here

答案 3 :(得分:1)

所有的功劳都归功于MarkoCen关于使用链式承诺的建议。这是我的最终解决方案:

app.js.coffee

angular
  .module 'dashboard', ['templates']

car_service.coffee

CarService = ($q, $http)->
  get = (url,defer)->
    $http.get(url).then(
      (result)->
        data = result.data
        if data.status == "started"
          get(url, defer)
          defer.notify(data.percentage_completion)
        else
          defer.resolve(data.data.cars)
    )
    defer.promise

  return{
    getAll: (url)->
      defer = $q.defer()
      get(url,defer)
  }

angular
  .module 'dashboard'
  .factory 'CarService', [
    '$q'
    '$http'
    CarService
  ]

cars_controller.coffee

DashboardController = (CarService) ->
  vm = @
  vm.campaigns = []
  vm.statusBar = {}

  CarService.getAll('/cars.json').then(
    (data)->
      vm.campaigns = data
    (reason)->
      console.log reason
    (update)->
      vm.statusBar.percentage = update
  )
  return

angular
  .module 'dashboard'
  .controller 'DashboardController', [
    'CarService'
    DashboardController
  ]