简体   繁体   English

角度异步调用

[英]Angular async calls

I have a bizarre issue happening in my Angular app and I think the async'ness of Angular is the cause but I'm not sure how I can fix it. 我的Angular应用程序中发生一个奇怪的问题,我认为Angular的异步性是原因,但我不确定如何解决。

On .run I have the following: .run我有以下内容:

app.run(function ($user, $soundcloud) {
    $user.set();
    $soundcloud.set();
});

and then the two services... 然后是两项服务...

app.service('$user', ['$http',  function ($http) {    
    var currentUser;    
    this.set = function () {
        $http.get('/users/active/profile')
            .success(function (obj) {
                currentUser = obj;
            })
            .error(function () {
                console.log('ERR');
            })
    }    
    this.fetch = function () {
        return currentUser;
    }

}]);    
app.service('$soundcloud', ['$http', function ($http) {    
    var sc;    
    this.set = function () {
        $http.get('/users/active/services/soundcloud')
            .success(function (obj) {
                sc = obj;
            })
            .error(function () {
                console.log('Error fetching soundcloud feed')
            })
    }    
    this.fetch = function () {
        return sc;
    }    
}]);

The issue I'm having is that when I go to /profile and reload the page, the data returned from $soundcloud.fetch() isn't available unless I wrap $soundcloud.fetch() inside a setTimeout() function which obviously isn't ideal so I'm wondering where I should place $soundcloud.set() to also make that data available immediately on page load. 我遇到的问题是,当我转到/ profile并重新加载页面时,从$ soundcloud.fetch()返回的数据不可用,除非我将$ soundcloud.fetch()包装在setTimeout()函数中,这显然是不是很理想,所以我想知道应该在哪里放置$soundcloud.set()以便在加载页面时立即使这些数据可用。

This is my controller currently: 这是我目前的控制器:

app.controller('profileController', ['$scope', '$user', '$soundcloud', function ($scope, $user, $soundcloud) {

    $scope.activeUser = $user.fetch();
    $scope.activeSoundcloud = $soundcloud.fetch();

    console.log($user.fetch());
    setTimeout(function () {
        console.log($soundcloud.fetch());
    },100);
}]);

If I take $soundcloud.fetch() outside of the setTimeout then I get undefined as the result. 如果我将$soundcloud.fetch()置于setTimeout之外,那么结果将undefined Where should $soundcloud.set/fetch be called in order to make both sets of data (user and soundcloud) immediately available? 为了使两组数据(用户和soundcloud)立即可用,应该在哪里调用$ soundcloud.set / fetch?

PLUNKER DEMO 朋克演示

One way to solve this problem is to fetch all the necessary data and then manually bootstrap the application. 解决此问题的一种方法是获取所有必需的数据,然后手动引导应用程序。

To do this, create bootstrapper.js . 为此,请创建bootstrapper.js You can access the built-in angular services without bootstrapping a module by angular.injector(['ng']).invoke() which accepts a function with dependencies that can be injected. 您可以通过angular.injector(['ng']).invoke()来访问内置的角度服务,而无需引导模块,该方法接受具有可注入依赖性的函数。 The urlMap variable is simply a key value store for the settings together with each of its respective urls. urlMap变量只是用于设置及其每个相应URL的键值存储。 You loop this urlMap via angular.forEach() and then get all settings one by one and store each settings in a settings variable along with each of its respective keys. urlMap通过angular.forEach()循环此urlMap ,然后一个个地获取所有设置,并将每个设置及其每个键存储在settings变量中。 Store each $http promise request in promises array to resolve it with $q.all() . 将每个$http promise请求存储在promises数组中,以使用$q.all()进行解析。 When all promises have been resolved, call bootstrap() function which adds a Settings value in app.settings module and then manually bootstraps the application. 解决所有承诺后,调用bootstrap()函数,该函数会在app.settings模块中添加一个Settings值,然后手动引导该应用程序。

bootstrapper.js bootstrapper.js

angular.injector(['ng']).invoke(function($http, $q) {

  var urlMap = {
    $user: '/users/active/profile',
    $soundcloud: '/users/active/services/soundcloud'
  };

  var settings = {};

  var promises = [];

  var appConfig = angular.module('app.settings', []);

  angular.forEach(urlMap, function(url, key) {
    promises.push($http.get(url).success(function(data) {
      settings[key] = data;
    }));
  });

  $q.all(promises).then(function() {
    bootstrap(settings);
  }).catch(function() {
    bootstrap();
  });

  function bootstrap(settings) {
    appConfig.value('Settings', settings);

    angular.element(document).ready(function() {
      angular.bootstrap(document, ['app', 'app.settings']);
    });
  }

});

app.js app.js

angular.module('app', [])

  .run(function(Settings) {
    if(Settings === null) {
      // go to login page or
      // a page that will display an
      // error if any of the requested
      // settings failed
    }
  })

  .controller('ProfileController', function($scope, Settings) {
    console.log(Settings);
  });

The result from $soundcloud.set() overrides the value from sc. $ soundcloud.set()的结果将覆盖sc的值。

If you tried to bind the value from $soundcloud.fetch to the scope before the $soundcloud.set() is returned it binds undefined to the scope, since the var sc is not yet defined. 如果在返回$ soundcloud.set()之前尝试将$ soundcloud.fetch中的值绑定到作用域,则由于未定义var sc,因此它将未定义绑定到作用域。 When $soundcloud.set() set the sc field, the scope variable will never get notified since it only got the undefined value and know nothing about sc. 当$ soundcloud.set()设置sc字段时,scope变量将永远不会得到通知,因为它仅获得未定义的值,而对sc一无所知。

The easiest sollution is to create a wrapper object that will never be removed. 最简单的解决方案是创建一个永远不会删除的包装对象。 Something like this. 这样的事情。

app.service('$soundcloud', ['$http', function ($http) {    
var sc = {};    // define an object instead of undefined
this.set = function () {
    $http.get('/users/active/services/soundcloud')
        .success(function (obj) {
            sc.soundcloud = obj; // write to a field on the object, 
                                 // do not override sc, since sc is already returned
        })
        .error(function () {
            console.log('Error fetching soundcloud feed')
        })
}    
this.fetch = function () {
    return sc;
}    
}]);

When you use the result of $soundcloud.fetch() you need to refer to the soundcloud field. 使用$ soundcloud.fetch()的结果时,需要引用soundcloud字段。 At least it will always get updated when $soundcloud.set is called 至少在调用$ soundcloud.set时,它将始终得到更新

You're right about it being a problem with the asynchronicity. 您认为这是异步问题是对的。

  1. The code in .run() triggers an asynchronous .get() . .run()的代码触发一个异步.get()
  2. Before the result comes back, and the callback passed to .success() is run, you run fetch() . 在结果返回并传递给.success()的回调运行之前,请运行fetch() As such, fetch() returns an undefined sc . 这样, fetch()返回一个未定义的sc
  3. You then console.log() this, hence you see undefined. 然后,您在console.log()看到,因此未定义。
  4. The promise gets resolved with the data from your .get() , and you assign sc to the result. 使用您的.get()的数据解析承诺,然后将sc分配给结果。

The approach @Andre Paap has just outlined is a good one - essentially by creating a blank reference object and updating one of its properties on the callback (rather than overwriting the reference completely), you can still assign this to the scope, and angular will 'magically' update the view once it's populated (using the data binding). @Andre Paap刚才概述的方法是一种很好的方法-本质上是通过创建一个空白引用对象并在回调上更新其属性之一(而不是完全覆盖该引用),您仍然可以将此方法分配给作用域,填充视图后(使用数据绑定)“神奇地”更新视图。 However , if you console.log() the result immediately, you'll still see the same thing. 但是 ,如果立即console.log()结果,您仍然会看到相同的内容。

In other words, if you had this inside your service: 换句话说,如果您的服务中包含以下内容:

var currentUser = {};    
this.set = function () {
    return $http.get('/users/active/profile')
        .success(function (obj) {
            currentUser.name = obj.name;
        })
        .error(function () {
            console.log('ERR');
        })
}    

And this in your template: 这在您的模板中:

<span ng-show="currentUser.name"> Hello {{currentUser.name}}! </span>

Then putting this in your controller would result in the above span showing up correctly once the set() call has returned: 然后将其放在您的控制器中将导致上述范围在set()调用返回后正确显示:

$scope.currentUser =  $user.fetch()
// NOTE: the below would still log undefined:
console.log($scope.currentUser.name) //<- undefined.

If you wanted to run some code only after the .set() call has been returned, then you can take advantage of the fact that $http methods return promises, like so: 如果您只想在返回.set()调用后才运行某些代码,则可以利用$http方法返回promise的事实,例如:

$user.set().then(function(){
  // This code runs *after* the call has returned.
  $scope.currentUser = $user.fetch()
  console.log($scope.currentUser.name) //<- "Ed"
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM