【问题标题】:Angular Service asynchronous singleton for UI-RouterUI-Router 的 Angular Service 异步单例
【发布时间】:2016-06-15 04:19:35
【问题描述】:

我正在为 Angular 应用程序使用 UI-Router。我有通过$http 异步调用提取的数据,该调用用于创建各种连接对象。我的目标是有某种方法来创建这个对象并使其作为单例可用。尽管发生这种情况的机会很少,但我希望避免服务中的 socket 属性被异步分配的时间问题。当路由器中的不同状态想要获取或创建另一个连接对象时,它将通过单例实现,以便可以使用相同的连接而不是创建另一个连接。

下面是我尝试开始工作的一些代码的框架。我不确定该州的resolve 是否可以做些什么。

关于如何做到这一点有什么想法或建议吗?

var myapp = angular.module('me', ['ui.router'])
.service('connectionService', ['$http', function($http) {
    var socket;

    // Somehow call and store result of $http in a singleton
    // Would the $http.get get put in some kind of function? If so, how should it get called 
    // and returned so that a user of the service can get the 'socket' object synchronously?
        $http.get("something/that/returns/data")
            .success(function (result) {
                this.socket= createConnection(result.data);
            })
            .error(function (err) {
                //handle error
            })

    var getSocket= function() {
        if (!this.socket) {
            return createNewSocket();
        } else {
            return this.socket;
        }
    }
}])

myapp.config(function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise("home");

    $stateProvider
        .state('home', {
            url: "/home",
            templateUrl: "home.html",
            resolve: {
                socket: // ??? Can something be done here ???
            },
            controller: function(socket) {
                // I can haz socket?
            }
        })
        .state('nextState', {
            url: "/next",
            templateUrl: "next.html",
            resolve: {
                socket: // Should be the same socket object as from the home state
            },
            controller: function(socket) {
                // I can haz same socket?
            }
        })
})

更新

在试图更好地解释问题的过程中,我已经更改了几次原始代码。 cmets 的人已经纠正了我所犯的明确错误。我已将上面的代码恢复到原来的样子,并在下面添加了新代码。我仍然遇到的问题是 ui-router 状态上的resolve 在进入控制器之前没有解决。因此,在应该解决的socket 对象上调用emit() 时,出现“无法读取未定义的属性'发射'”的错误。我还希望 socket 对象在状态之间是相同的对象。我将不胜感激任何其他建议或 cmet 引导我朝着正确的方向发展。

var myapp = angular.module('me', ['ui.router'])
.service('connectionService', ['$http', function($http) {
    var socket;

    // Somehow call and store result of $http in a singleton
    // Would the $http.get get put in some kind of function? If so, how should it get called 
    // and returned so that a user of the service can get the 'socket' object synchronously?
    $http.get("something/that/returns/data")
        .then(function (result) {
            socket = createConnection(result.data);
        }, function (err) {
            // handle error
        });

    var getSocket = function() {
        if (!socket) {
            // handle error
        } else {
            return socket;
        }
    }
}])

myapp.config(function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise("home");

    $stateProvider
        .state('home', {
            url: "/home",
            templateUrl: "home.html",
            resolve: {
                socket: function(connectionService) {
                    return connectionService.getSocket();
                }
            },
            controller: function(socket) {
                socket.emit("some msg");
            }
        })
        .state('nextState', {
            url: "/next",
            templateUrl: "next.html",
            resolve: {
                socket: function(connectionService) {
                    return connectionService.getSocket();
                }
            },
            controller: function(socket) {
                socket.emit("some other msg");
            }
        })
})

更新 2

可行的解决方案,但这不如Muli Yulzari 的答案优雅,该答案已被接受为正确答案。

我研究了创建自己的 Promise 并将其作为 ui-router 解决的问题返回。根据我所有的调试和堆栈跟踪,在状态之间只创建了一个连接,并且延迟进入控制器,直到连接解决。所以,我相信这是一个可行的解决方案。我还有一个代码版本,它在解决承诺之前处理连接上的onConnect 事件,但为了简洁起见,我将其省略了。

var myapp = angular.module('me', ['ui.router'])
.service('connectionService', ['$http', '$q', function($http, $q) {
    var socket;

    this.getSocket = function() {
        if (!socket) {
            var deferred = $q.defer();

            $http.get("something/that/returns/data")
                .then(function (result) {
                    socket = createConnection(result.data);
                    deferred.resolve(socket);
                }, function (err) {
                    // handle error
                    deferred.reject(socket);
                });

            return deferred.promise;
        } else {
            return socket;
        }
    }
}])

myapp.config(function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise("home");

    $stateProvider
        .state('home', {
            url: "/home",
            templateUrl: "home.html",
            resolve: {
                socket: function(connectionService) {
                    return connectionService.getSocket();
                }
            },
            controller: function(socket) {
                socket.emit("some msg");
            }
        })
        .state('nextState', {
            url: "/next",
            templateUrl: "next.html",
            resolve: {
                socket: function(connectionService) {
                    return connectionService.getSocket();
                }
            },
            controller: function(socket) {
                socket.emit("some other msg");
            }
        })
})

【问题讨论】:

  • 在 Angular 中,服务是单例的,所以这确实是你想要的。但是,您的服务构建方式存在一些问题;具体来说,您在回调中使用 .this 将无法按预期工作,因为 .this 指的是回调,而不是父服务。此外,您应该考虑切换到.then,而不是已弃用的.success
  • 删除了已弃用的 .success 并切换到 .then 承诺。
  • 你还在promise中使用.this,这是主要部分不起作用。
  • 在角度服务和工厂中都是单例的。
  • 如果您是 Javascript 新手,那么您将需要花一些时间了解 this 的工作原理,因为它是常见的错误来源。

标签: javascript angularjs asynchronous angular-ui-router


【解决方案1】:

据我了解,您希望每次套接字实例无效时都根据来自$http.get 的数据初始化套接字,但在应用加载时异步执行此操作。

.service('connectionService', ['$http', '$q', function($http, $q) {
    //For caching the socket
    var socket;

    //Do the first get
    tryGetData();

    //tryGetData executes $http.get and returns a promise that when fulfilled -
    //returns the new socket instance.
    function tryGetData(){
     return $http.get("something/that/returns/data")
        .then(function (result) {
            return socket = createConnection(result.data);
        }, function (err) {
            // handle error
        });
    }

    //Whenever getSocket gets called, it first checks if the socket is instantiated. if not -
    //assumes the $http.get failed and tries again to instantiate the socket calling
    //$http.get again in the process.
    //this function returns a promise for controllers to rely on.
    this.getSocket = function(){
     if(socket) return $q.resolve(socket);
     return tryGetData();
    }
}]);

您的代码的其余部分看起来不错。

【讨论】:

  • 这很接近,但 tryGetData() 不会返回 ui-router 的解析可以处理的承诺。我有一个解决方案,可以在其中创建一个 $q 延迟对象并返回该延迟对象的承诺。我将发布该解决方案,但希望感谢您的帮助。
  • 是的,$http 确实返回了一个承诺。但是这里写的方式,tryGetData() 不会返回那个承诺,因为.then() 已经被调用了。不知道如何用适当的术语来解释它。此外,我需要一种方法来处理从$http.get 返回的数据,然后再返回承诺结果。因此,我需要抓住$http 的承诺并重新抛出另一个承诺。至少这是我的观察。我可能完全错了......
  • 另外值得注意的是,由于 Promise 是 monad,这正是您可以像这样执行链接的原因。
  • 太棒了。我不知道我之前尝试过这个时是否忽略了重新加载我的代码或其他东西。它现在肯定可以工作,并且肯定会删除我添加的所有额外内容。我当然学到了很多。谢谢大家的帮助!
猜你喜欢
  • 2013-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
相关资源
最近更新 更多