Http服务工厂返回未定义到Angular中的控制器

时间:2016-05-10 00:13:15

标签: javascript angularjs promise angular-promise angular-http

我有这项服务,初始化品牌和实际品牌变量。

.factory('brandsFactory', ['$http', 'ENV', function brandsFactory($http, ENV){

    var actualBrand = {};
    var brands = getBrands(); //Also set the new actualBrand by default

    function getBrands(){
        var URL;
        console.log('MOCKS enable? ' +  ENV.mocksEnable);
        if(ENV.mocksEnable){
           URL = ENV.apiMock + ENV.getBrandsMock;
        }else{
            URL = ENV.apiURL + ENV.getBrands;
        }

       $http.get(URL).
        success(function(data){
            console.log('data is :' +  data);
            actualBrand = data[0];
            console.log('Actual brand is ' + actualBrand);
            return data;
        }).
        error(function(){
        console.log('Error in BrandsController');
     });

    }

    var setActualBrand = function(activeBrand) {
        actualBrand = activeBrand;
    };

    var isSetAsActualBrand = function(checkBrand) {
        return actualBrand === checkBrand;
    };

    return {
        brands : brands,
        actualBrand : actualBrand
    }; 

我的控制器看起来像这样:

/* BrandController to manage the html content of a single brand */
    .controller('BrandCController', function(brandsFactory, $scope){

        $scope.brands = brandsFactory.brands;
        console.log('brands in controller is ' + $scope.brands + ' -- ' + brandsFactory.brands);

    });

控制器中的问题是它在brandsFactory.brands中未定义,因为它在服务之前加载。

为什么我可以解决这个问题? 我是角度和JavaScript的新手,也许我做了很多错事。 任何帮助,我将不胜感激,谢谢。

4 个答案:

答案 0 :(得分:3)

SmartPointerManager.getInstance(element.getProject()).createSmartPsiElementPointer(element); 的原因实际上是因为undefined请求的异步性质。没有什么(嗯,基本上没什么)你可以改变这种行为。您将不得不使用最常见的方式解决此问题(如果您继续使用Angular和大多数Web开发语言,这将成为一项长期的实践)并使用promise。

什么是承诺?它的在线指南很多。向您解释它的最基本方法是在异步操作返回之前不会执行promise。

https://docs.angularjs.org/api/ng/service/$q
http://andyshora.com/promises-angularjs-explained-as-cartoon.html

现在,当你$http属性时,它可能/可能没有被加载(实际上,它可能没有),因此你得到console.log

此外,我对此并不十分确定,但您使用的是工厂提供商,但是您没有从提供程序功能返回任何内容,因此您正在使用它,就好像它是一项服务一样。我不能100%确定Angular是否允许这样做,但当然最好的做法是返回你想要从工厂创建的实例。

答案 1 :(得分:2)

  

我在角度和JavaScript方面真的很新,也许我做了很多错事。任何帮助,我将不胜感激,谢谢。

你是对的 - 你做了很多错事。让我们从getBrands函数开始:

//Erroneously constructed function returns undefined
//
function getBrands(){
    var URL;
    console.log('MOCKS enable? ' +  ENV.mocksEnable);
    if(ENV.mocksEnable){
       URL = ENV.apiMock + ENV.getBrandsMock;
    }else{
        URL = ENV.apiURL + ENV.getBrands;
    }

   $http.get(URL).
    success(function(data){
        console.log('data is :' +  data);
        actualBrand = data[0];
        console.log('Actual brand is ' + actualBrand);
        //SUCCESS METHOD IGNORES RETURN STATEMENTS
        return data;
    }).
    error(function(){
    console.log('Error in BrandsController');
 });

return data语句在函数内嵌套 ,该函数是$http .success方法的参数。它确实将数据返回到getBrands函数。由于return正文中没有getBrands语句,getBrands函数会返回undefined

此外,.success服务$http方法会忽略返回值。因此,$http服务将返回一个解析为response对象而非data对象的promise。要返回解析为data对象的承诺,请使用.then方法。

//Correctly constructed function that returns a promise
//that resolves to a data object
//
function getBrands(){
    //return the promise
    return (
        $http.get(URL)
            //Use .then method
            .then (function onFulfilled(response){
                //data is property of response object
                var data = response.data;
                console.log('data is :' +  data);
                var actualBrand = data[0];
                console.log('Actual brand is ' + actualBrand);
                //return data to create data promise
                return data;
            })
     )
}

return函数中包含onFulfilled语句,return函数正文中包含getBrands语句,getBrands函数将返回一个承诺通过data完成解析(或使用response对象拒绝解析。)

在控制器中,使用.then.catch方法解决承诺。

brandsFactory.getBrands().then(function onFulfilled(data){
    $scope.brand = data;
    $scope.actualBrand = data[0];
}).catch( function onRejected(errorResponse){
    console.log('Error in brands factory');
    console.log('status: ', errorResponse.status);
});
  

Deprecation Notice1

     

已弃用$http遗留承诺方法.success.error。改为使用标准.then方法。

更新

  

我使用$q承诺来解决它。我在控制器中使用.then,但我无法在工厂中使用它。如果你想检查它。并感谢您的反馈。

//Classic `$q.defer` Anti-Pattern
//
var getBrands = function(){

    var defered = $q.defer();
    var promise = defered.promise;

    $http.get(URL)
    .success(function(data){
        defered.resolve(data);
    })
    .error(function(err){
        console.log('Error in BrandsController');
        defered.reject(err);
    });

   return promise;    
};//End getBrands

这是classic $q.defer Anti-Pattern.sucesss.error方法被弃用的原因之一是,由于这些方法忽略返回值,他们会鼓励这种反模式。

这种反模式的主要问题是它打破了$http承诺链,当错误条件没有得到妥善处理时,承诺会挂起。另一个问题是,程序员通常无法在后续的函数调用中创建新的$q.defer

//Same function avoiding $q.defer   
//
var getBrands = function(){
    //return derived promise
    return (
        $http.get(URL)
          .then(function onFulfilled(response){
            var data = response.data;
            //return data for chaining
            return data;
        }).catch(function onRejected(errorResponse){
            console.log('Error in BrandsController');
            console.log('Status: ', errorResponse.status;
            //throw value to chain rejection
            throw errorResponse;
        })
    )
};//End getBrands

此示例显示如何记录拒绝并将errorResponse对象抛出链以供其他拒绝处理程序使用。

有关详细信息,请参阅Angular execution order with $q

另外,Why is angular $http success/error being deprecated?

而且,Is this a “Deferred Antipattern”?

答案 2 :(得分:1)

我认为您遇到的问题是您的控制器正在尝试访问由$ http.get调用设置的属性。 $ http.get返回一个promise,它是对您提供的用于获取品牌的URL的异步调用。这意味着拨打电话的控制器不会等待品牌加载。

有几个选项可以解决此问题,但您只需从服务中返回承诺并在控制器中解决它。

function getBrands(){
            var URL;
            console.log('MOCKS enable? ' +  ENV.mocksEnable);
            if(ENV.mocksEnable){
               URL = ENV.apiMock + ENV.getBrandsMock;
            }else{
                URL = ENV.apiURL + ENV.getBrands;
            }

           return $http.get(URL);
         });

然后在您的控制器中,您可以将成功和错误功能放在......

brandsFactory.brands().then(function(response){
  $scope.brand = response.data;
  $scope.actualBrand = response.data[0];
}, function(err){
   console.log('Error in brands factory');
});

还有其他选项(例如延迟加载或将工厂直接放在您的示波器上),但如果您愿意,可以查看这些选项。

答案 3 :(得分:0)

我使用$ q promise来解决它。

.factory('brandsFactory', ['$http', '$q', 'ENV', function brandsFactory($http, $q, ENV){

        var actualBrand = {};

        var getBrands = function(){
            var URL;
            var defered = $q.defer();
            var promise = defered.promise;

            if(ENV.mocksEnable){
                URL = ENV.apiMock + ENV.getBrandsMock;
            }else{
                URL = ENV.apiURL + ENV.getBrands;
            }

            $http.get(URL)
            .success(function(data){
                actualBrand = data[0];
                defered.resolve(data);
            })
            .error(function(err){
                console.log('Error in BrandsController');
                defered.reject(err);
            });

            return promise;    
        };//End getBrands

        var setActualBrand = function(activeBrand) {
            actualBrand = activeBrand;
        };

        var isSetAsActualBrand = function(checkBrand) {
            return actualBrand === checkBrand;
        };

        return {
            setActualBrand : setActualBrand,
            getBrands : getBrands
        }; 

    }])//End Factory

在我的控制器中:

 /* BrandController to manage the html content of a single brand */
    .controller('BrandCController', function(brandsFactory, $scope){
        $scope.brands = [];
        $scope.actualBrand = {};
        $scope.setActualBrand = function(brand){
            brandsFactory.setActualBrand(brand);
            $scope.actualBrand = brand;
        };

        brandsFactory.getBrands()
        .then(function(data){
            $scope.brands = data;
            $scope.actualBrand = data[0];
        })
        .catch(function(errorResponse){
            console.log('Error in brands factory, status : ', errorResponse.status);
        });
    });//End controller

如果我能改进答案,请告诉我。我用它从以前的答案得到的所有信息。谢谢任何帮助过的人。

<强>更新

删除工厂中的$ q.defer Anti-Pattern

.factory('brandsFactory', ['$http', '$q', 'ENV', function brandsFactory($http, $q, ENV){

    var actualBrand = {};
    var getBrands = function(){
        var URL;

        if(ENV.mocksEnable){
            URL = ENV.apiMock + ENV.getBrandsMock;
        }else{
            URL = ENV.apiURL + ENV.getBrands;
        }

       return (
        $http.get(URL)
          .then(function onFulfilled(response){
            var data = response.data;
            //return data for chaining
            return data;
        }).catch(function onRejected(errorResponse){
            console.log('Error in BrandsController');
            console.log('Status: ', errorResponse.status);
            return errorResponse;
        })
        );

    };//End getBrands

    var setActualBrand = function(activeBrand) {
        actualBrand = activeBrand;
    };

    return {
        setActualBrand : setActualBrand,
        getBrands : getBrands
    }; 
}]);//End Factory