2015-10-05 15 views
6

Zbudowałem nieco skomplikowaną metodę zwracania zasobów za pomocą $ http.

Metoda zwraca obietnicę, a następnie sprawdza moją lokalną pamięć podręczną, jeśli zasoby jeszcze istnieją. Jeśli to zrobi, zwróci buforowane zasoby, jeśli nie, wykona żądanie $ http. Działa to świetnie po buforowaniu zasobu, ale mam wiele funkcji za pośrednictwem aplikacji, która uderza w tę metodę podczas ładowania, a każda z nich wykona żądanie http, ponieważ zasoby nie zostały jeszcze zwrócone i zapisane w pamięci podręcznej.

Wpadłem na prosty test, który to rozwiązuje, ale wydaje mi się, że powinien istnieć lepszy sposób. Dodałem wartość logiczną, która jest ustawiona na wartość true, jeśli metoda znajduje się w trakcie pobierania zasobu, a jeśli tak, to rozwiązuję tę metodę z półsekundowym limitem czasu, aby dać czas żądania do rozwiązania. Kod znajduje się poniżej.

Czy istnieje lepszy sposób?

var schools = []; 
    var loadingSchools = false; 

    function getAllSchools(forceUpdate) { 
     return $q(function (resolve, reject) { 
      if(loadingSchools) resolve($timeout(getAllSchools, 500)); 

      else{ 

       loadingSchools = true; 

       if (schools.length && !forceUpdate) { 
        loadingSchools = false; 
        resolve(schools); 
        return; 
       } 

       console.log('$http: Getting All Schools - schoolService.js'); 

       $http.get(API_PATH + 'schools_GetAll', {cache:true}) 
       .success(function(result) { 
        schools = result; 
        loadingSchools = false; 
        resolve(schools); 
       }) 
       .error(function(error) { 
        schools = []; 
        loadingSchools = false; 
        reject(error); 
       }); 
      } 
     }); 
    } 
+1

może utworzyć funkcję, która zarządza oczekiwania obiecuje więc w zasadzie zamiast ustawiania limitu czasu się zarejestrować do tej funkcji, a gdy oryginał obietnica zwraca rozwiązać wszystkie te, które zarejestrowały – Saar

+2

znacznie łatwiej użyć rozwiązania w routerze, szczególnie jeśli używa się 'ui-routera '. Z jakiego routera korzystasz? – charlietfl

+0

@Saar - Dobre myślenie, rozważałem zrobienie czegoś takiego, ale chciałem zrobić ten post pierwszy. Pomyślałem, że istnieje metoda na to, wbudowany w $ http, który mógłbym wykorzystać. – Kolby

Odpowiedz

20

Po pierwsze, nie sądzę, że to konieczne, aby owinąć HttpPromise do innej obietnicy. Metodą success/errordeprecated powinieneś polegać wyłącznie na metodzie then() i traktować HttpPromise jako zwykłą obietnicę.

Aby upewnić się, że żądanie zostanie wysłane tylko raz, możesz faktycznie śledzić pierwsze utworzone przez siebie HttpPromise, a po kolejnych wywołaniach funkcji zwrócić tę samą obietnicę.

Oto usługa, która zaakceptuje punkt końcowy interfejsu API jako argument i zapewni, że tylko jedno żądanie zostanie wysłane do tego interfejsu API.

app.factory('$httpOnce', [ '$http', '$cacheFactory', 
    function ($http, $cacheFactory) { 
    var cache = $cacheFactory('$httpOnce'); 

    return function $httpOnce(url, options) { 
     return cache.get(url) || cache.put(url, $http.get(url, options) 
     .then(function (response) { 
      return response.data; 
     })); 
    }; 
    } 
]); 

Wykorzystanie

function log(data) { 
    console.log(data); 
} 

// issues an HTTP request 
$httpOnce('https://api.github.com/').then(log); 
// does not issue an HTTP request, returns the same promise as above 
$httpOnce('https://api.github.com/').then(log); 

// ... 
// HTTP request completes somewhere, both promises above are resolved 
// ... 

setTimeout(function() { 
    // immediately resolved 
    $httpOnce('https://api.github.com/').then(log); 
}, 5000); 

Oto demo. W narzędziach dewelopera można zobaczyć tylko jedno żądanie.

+0

Dokładnie tego, czego szukałem. Dziękuję za odpowiedź. – Kolby

+0

Mam pytanie dotyczące tego rozwiązania, chcę lepiej zrozumieć, dlaczego to działa. Po umieszczeniu wyniku $ http.get w pamięci podręcznej, uruchamiasz także obietnicę (aby $ http.get (adres URL, opcje) zwróciło obietnicę i użyłeś jej) przed dodaniem jej do pamięci podręcznej). Dlaczego możesz użyć innego.then() podczas pobierania pamięci podręcznej? Nie ma obietnicy zwróconej z "cache.get". $ http.get zwraca jedną obietnicę i zwraca wynik - zwraca inną obietnicę lub to samo? Dziękuję za poświęcony czas, daj mi znać, jeśli masz jakieś referencje, aby przeczytać o tym więcej. – florinmtsc

+0

Z pewnością flaga 'cache: true' robi to samo? Czy czegoś brakuje? – zilj

0

Właśnie dokładnie ten sam problem i tutaj jest moje usługa

app = angular.module('MM_Graph') 

class Team_Data 
    constructor: ($routeParams,$rootScope, $cacheFactory, $q, API)-> 
    @.$routeParams = $routeParams 
    @.$rootScope = $rootScope 
    @.$cacheFactory = $cacheFactory 
    @.cache   = $cacheFactory('team_Data') 
    @.$q   = $q 
    @.deferred  = null 
    @.API   = API 
    @.project  = null 
    @.data   = null 
    @.team   = null 
    @.schema  = null 


    call_With_Cache: (method, params, callback)=>         # method that uses promises to prevent multiple parallel calls 

    cache_Key = "#{method}_#{JSON.stringify(params)}"       # create cache key using method and params 

    if not @.cache.get cache_Key            # if this is the first require for this type of data (method_project_team) 
     deferred = @.$q.defer()             # create a new instance of deferred 
     @.cache.put cache_Key, deferred.promise         # put its promise in the cache 

     on_Data_Received = (data)->            # simple callback method to resolve the promise 
     deferred.resolve(data)             # resolve promise (this is the data that will show as the 1st param in 'then') 

     method_Params = params.concat(on_Data_Received)       # append on_Data_Received callback method to the current 'method' params list 

     @.API[method].apply(null, method_Params)         # invoke 'method' in @.API 

    @.cache.get(cache_Key).then (data)->          # invoke the 'then' from the promise (will happen async until data is recevied) 
     callback (data)               # finally call the original callback with the data received from 'method' 

    clear_Data:()=> 
    @.cache.removeAll() 
    @.scores = null 
    @.schema = null 
    @.data  = null 
    @.deferred = null 

    load_Data: (callback)=> 
    if not (@.$routeParams.project and @.$routeParams.team)      # check that project and team are set 
     return callback() 

    if (@.$routeParams.project is @.project and @.$routeParams.team is @.team) # check if either the project or team have changed 
     # do nothing here since project or team have not changed 
    else 
     @.clear_Data()               # when project changes remove all cache entries 
     @.project = @.$routeParams.project 
     @.team = @.$routeParams.team 
     @.project_Schema (schema)=>            # get projecg schema 
     @.data_Score (scores)=>             # get current team scores 
      @.team_Get (data)=>             # get team data 
      @.scores = scores 
      @.schema = schema 
      @.data = data 

      @.deferred.resolve()            # trigger promise resolve 

    @.deferred ?= @.$q.defer()             # ensure @.deferred object exits 
    @.deferred.promise.then ->             # schedule execution 
     callback()                # invoke original caller callback 

    data_Score : (   callback) => @.call_With_Cache 'data_Score'  , [@.project, @.team  ], callback 
    project_Schema: (   callback) => @.call_With_Cache 'project_Schema' , [@.project    ], callback 
    radar_Fields : (   callback) => @.call_With_Cache 'data_Radar_Fields', [@.project    ], callback 
    radar_Team : (target_Team, callback) => @.call_With_Cache 'data_Radar_Team' , [@.project, target_Team ], callback 
    team_Get  : (   callback) => @.call_With_Cache 'team_Get'   , [@.project, @.team  ], callback 

    save: (callback)=> 
    @.API.file_Save @.project, @.team , @.data, callback 

app.service 'team_Data', ($routeParams, $rootScope, $cacheFactory, $q, API)=> 
    return new Team_Data $routeParams, $rootScope, $cacheFactory, $q, API 
Powiązane problemy