链接承诺,或一个承诺触发另一个

Posted

技术标签:

【中文标题】链接承诺,或一个承诺触发另一个【英文标题】:linking promises, or one promise triggers another 【发布时间】:2013-11-25 04:02:30 【问题描述】:

我正在构建一个天气应用程序,我首先必须获取用户的位置,然后发出获取天气的请求。

所以我有一个GeolocationService 和一个WeatherService。我的WeatherService 目前正在调用Geolocation 服务。如何让WeatherService 等到它有来自GeolocationService 的结果后再发出HTTP 请求?

app.factory('GeolocationService',function($q,$window,$rootScope) 返回 getLatLon:函数() var deferred = $q.defer(); 如果(!window.navigator) $rootScope.$apply(function() deferred.reject(new Error("地理位置不可用")); ); 别的 $window.navigator.geolocation.getCurrentPosition(函数(位置) $rootScope.$apply(function() deferred.resolve(位置); ); ,函数(错误) $rootScope.$apply(function() deferred.reject(错误); ); ); 返回 deferred.promise; ; ); app.factory("WeatherService", function ($q,$http,$rootScope, GeolocationService) 返回 获取天气:函数() var 天气; var loc = new GeolocationService.getLatLon(); var lat= loc.lat || 37.4568202221774, lon= loc.lon || -122.201366838789; 变量单位 = ''; var url = 'http://api.openweathermap.org/data/2.5/forecast/daily?lat='+lat+'&lon='+lon+'&units='+units+'&callback=JSON_CALLBACK'; $http.jsonp(网址) .成功(功能(数据) 天气=数据; 返回天气; ) .错误(函数(错误) 天气=错误; 返回错误; ); ; );

【问题讨论】:

真的不认为你需要在$apply 中包含resolve 和reject,不知道是什么想法 @charlietfl - 我试过不将解析包装在 $apply 中,但没有它就不会触发 .then。 【参考方案1】:

你只需要在你的服务中使用承诺,然后链接承诺。对于示例,我有一个 user.company_id 的用户,如果我想获得公司的名称,我必须等待用户加载。和你的情况一样。

这是我的服务:

    angular.module('UserService', []) 
.factory('UserService', function($q , $http, $rootScope,$timeout) 
var currentUserPromise = null;
var currentCompanyPromise = null;
return 
    getCurrentUser: function() 
        if (currentUserPromise === null) 
            var config = ;
            config.cache = true;
            config.method = "GET"; 
            config.url = "users/get_current_user";
            currentUserPromise = $http(config)
                .then(function(response) 
                    if (typeof response.data === 'object') 
                        return response.data.user;
                     else 
                        // invalid response
                        return $q.reject(response.data);
                      
                , function(response) 
                    // something went wrong
                    return $q.reject(response.data);
                );
        
        return currentUserPromise;
    ,

    getCurrentCompany: function(company_id) 
        if (currentCompanyPromise === null)
            var config = ;
            var company = ;
            company.id = company_id; 
            config.cache = true;
            config.method = "GET"; 
            config.url = "/companies/show";
            config.params = company;
            currentCompanyPromise = $http(config)
                .then(function(response) 
                    if (typeof response.data === 'object') 
                        return response.data.company;
                     else 
                        // invalid response
                        return $q.reject(response.data);
                    

                , function(response) 
                    // something went wrong
                    return $q.reject(response.data);
                );
            
            return currentCompanyPromise;

        
    ;     
);

在我的控制器中,我这样使用:

控制器:

var promiseCurrentUser = UserService.getCurrentUser();
promiseCurrentUser.then(function(user) 
    $scope.currentUser = user;
    UserService.getCurrentCompany(user.company_id).then(function(company)
        $scope.companyName = company.name;
    );
);

promise 最酷的地方在于它是一次性解决的。

希望对你有帮助。

【讨论】:

【参考方案2】:

您的GeolocationService 的函数getLatLon 正在返回一个承诺。无需使用new 运算符调用它。

您的getWeather 函数应该类似于:

app.factory("WeatherService", function ($q,$http,$rootScope, GeolocationService) 
    return 
        getWeather: function()
            var weather;
            return GeolocationService.getLatLon().then(
                function (loc) 
                    var  lat= loc.lat || 37.4568202221774, 
                    lon= loc.lon || -122.201366838789 ;
                    var units = '';
                    var url = 'http://api.openweathermap.org/data/2.5/forecast/daily?lat='+lat+'&lon='+lon+'&units='+units+'&callback=JSON_CALLBACK';
                    return $http.jsonp(url)
                        .success(function(data) 
                            weather=data;
                            return weather;
                        )
                        .error(function(err)
                            weather=err;
                            return err;
                        );
            );
        
    

在这里,我们首先调用GeolocationService.getLatLon() 来获得承诺。然后我们将我们的处理链作为一个链链接到它。成功函数会得到position,你在这里解决它deferred.resolve(position);

此外,您不需要将resolvereject 包装在$apply 中。因此,您的GeoLocationService 可以简化为:

            if(!window.navigator)
                deferred.reject(new Error("Geolocation not available"));
             else 
                $window.navigator.geolocation.getCurrentPosition(function(position)
                        deferred.resolve(position);
                , function(error)
                        deferred.reject(error);
                );
            

【讨论】:

感谢@musically_ut,如果不包装延迟,.then 不会被触发。此外,从成功语句返回 weather 不会将 weather 传递给范围。我的范围只是$scope.weather = WeatherModel.getWeather(); 您使用的是哪个版本的 Angular?最新版本 (1.2) makes promises A+ compliant,它们应该在不需要额外的 $digest 循环的情况下触发。 weather 传递给范围,但作为一个承诺。如果您的任何操作依赖于weather 的值,请将它们包含在一个链中:$scope.weather.then(function (weatherData) ... )。或者在 then: WeatherModel.getWeather().then(function (weatherData) $scope.weather = weatherData; ) 中分配 weather。不过,这两者都是异步 这就解释了,我正在使用 v 1.5,希望在即将发布的版本中升级。我尝试了链接“then”的建议,但收到错误“无法调用未定义匿名函数的方法”。 刚刚又测试了一遍,我的电话里肯定有()

以上是关于链接承诺,或一个承诺触发另一个的主要内容,如果未能解决你的问题,请参考以下文章

链接猫鼬承诺的语法

将承诺的结果链接并传递给进一步的承诺[重复]

如何让 React Redux 异步操作返回一个承诺?

链接承诺不会将数据从一个 .then 传递到下一个

在瀑布中链接承诺

在 for 循环中链接猫鼬承诺