2016-12-05 4 views
0

Я использую Angular 1.5.8. Представления в моем приложении требуют разных комбинаций из тех же 3 запросов ajax. Некоторые представления требуют данных от всех трех, другие требуют данных от двух или даже одной конечной точки.Цепочка Несколько дополнительных запросов Async Ajax

Я работаю над функцией, которая будет управлять извлечением этих данных, требуя, чтобы приложение только вызывало каждую конечную точку один раз. Я хочу, чтобы запросы ajax вызывались по мере необходимости, но только при необходимости. В настоящее время я создал функцию, которая работает, но, похоже, она может использовать улучшения.

Следующая функция содержится в пределах $rootScope. Он использует функцию fetchData() для циклического прохождения запросов на получение по запросу. Когда данные извлекаются, они сохраняются в глобальной переменной $ rootScope.appData, а затем fetchData() вызывается снова. Когда все данные извлекаются, отложенное обещание разрешается, и данные возвращаются контроллеру.

$rootScope.appData = {}; 

$rootScope.loadAppData = function(fetch) { 
    var deferred = $q.defer(); 

    function getUser() { 
    $http 
     .get('https://example.com/api/getUser') 
     .success(function(result){ 
     $rootScope.appData.currentUser = result; 
     fetchData(); 
     }); 
    } 

    function getPricing() { 
    $http 
     .get('https://example.com/api/getPricing') 
     .success(function(result) { 
     $rootScope.appData.pricing = result; 
     fetchData(); 
     }); 
    } 

    function getBilling() { 
    $http 
     .get('https://example.com/api/getBilling') 
     .success(function(result) { 
     $rootScope.appData.billing = result; 
     fetchData(); 
     }); 
    } 

    function fetchData() { 
    if (fetch.user && !$rootScope.appData.currentUser) { 
     getUser(); 
    } else if (fetch.pricing && !$rootScope.appData.pricing) { 
     getPricing(); 
    } else if (fetch.billing && !$rootScope.appData.billing) { 
     getBilling(); 
    } else { 
     deferred.resolve($rootScope.appData); 
    } 
    } 

    if ($rootScope.appData.currentUser && $rootScope.appData.pricing &&$rootScope.appData.billing) { 
    deferred.resolve($rootScope.appData); 
    } else { 
    fetchData(); 
    } 

    return deferred.promise; 
}; 

Объект fetch представлен как атрибут, этот объект показывает, какие Ajax запросы на вызов. Пример вызова к $rootScope.loadAppData() где будет запрашиваться только пользователя и данные о ценах будет выглядеть следующим образом:

$rootScope.loadAppData({user: true, pricing: true}).then(function(data){ 
    //execute view logic. 
}); 

Я интересно:

  1. Если сцепление этих функций можно сделать иначе? Достаточно ли функция fetchData(), или это странный способ выполнить эту функцию?
  2. Есть ли способ вызвать все необходимые запросы Ajax одновременно, но дождаться завершения всех необходимых вызовов до разрешения обещания?
  3. Необычно хранить данные как в $rootScope?

Я знаю, что эта функция в настоящее время неправильно обрабатывает ошибки. Это функциональность, которую я добавлю перед использованием этого фрагмента, но не имеет отношения к моему вопросу.

ответ

2

Вместо использования метода .success, используйте данных в .then методе и возврата к обработчику успеха:

function getUserPromise() { 
    var promise = $http 
     .get('https://example.com/api/getUser') 
     .then(function successHandler(result) { 
      //return data for chaining 
      return result.data; 
     }); 
    return promise; 
} 

Используйте услугу вместо $ rootScope:

app.service("myService", function($q, $http) { 

    this.loadAppData = function(fetchOptions) { 

     //Create first promise 
     var promise = $q.when({}); 

     //Chain from promise 
     var p2 = promise.then(function(appData) { 
      if (!fetchOptions.user) { 
       return appData; 
      } else { 
       var derivedPromise = getUserPromise() 
        .then(function(user) { 
        appData.user = user; 
        //return data for chaining 
        return appData; 
       }); 
       return derivedPromise; 
      ); 
     }); 

     //chain from p2 
     var p3 = p2.then(function(appData) { 
      if (!fetchOptions.pricing) { 
       return appData; 
      } else { 
       var derivedPromise = getPricingPromise() 
        .then(function(pricing) { 
        appData.pricing = pricing; 
        //return data for chaining 
        return appData; 
       }); 
       return derivedPromise; 
      ); 
     }); 
 //chain from p3 
     var p4 = p3.then(function(appData) { 
      if (!fetchOptions.billing) { 
       return appData; 
      } else { 
       var derivedPromise = getBillingPromise() 
        .then(function(user) { 
        appData.billing = billing; 
        //return data for chaining 
        return appData; 
       }); 
       return derivedPromise; 
      ); 
     }); 

     //return final promise 
     return p4; 
    } 
}); 

Приведенный выше пример создает обещание для пустого объекта. Затем он объединяет три операции. Каждая операция проверяет, требуется ли выборка. При необходимости выполняется выборка, и результат присоединен к объекту appData; если никакой выборки не требуется, объект appData передается следующей операции в цепочке.

ПРИМЕНЕНИЕ:

myService.loadAppData({user: true, pricing: true}) 
    .then(function(appData){ 
    //execute view logic. 
}).catch(functon rejectHandler(errorResponse) { 
    console.log(errorResponse); 
    throw errorResponse; 
}); 

Если какие-либо из операций извлечения из строя, последующие операции в цепи будут пропущены и конечный обработчик отвергает будет называться.

Поскольку вызов метода обещания возвращает новое производное обещание, легко создать цепочку обещаний. Можно создавать цепочки любой длины, и поскольку обещание может быть разрешено с помощью другого обещания (которое отложит дальнейшее его разрешение), можно приостановить/отложить разрешение обещаний в любой точке цепи. Это позволяет реализовать мощные API. - AngularJS $q Service API Reference - Chaining Promises

+0

Спасибо за этот ответ. Прочитав ваш ответ/сделав больше исследований, для меня совершенно очевидно, что эта логика должна содержаться в сервисе. Я сделал это изменение в своем проекте сейчас. Знаете ли вы, как выполнить все требуемые вызовы параллельно, а не последовательно, а затем вернуть результат, когда какой-либо вызов займет больше времени? – kravse

+1

Используйте '$ q.all', чтобы делать XHR параллельно. Последовательный метод полезен, когда следующий XHR зависит от информации из предыдущего XHR. Примером может служить расчет стоимости доставки после извлечения местоположения пользователей. – georgeawg

0

Обнаружили отличный способ ответить вопрос 2 в оригинальном посте. Использование $q.all() позволяет выполнять обеими обещаниями одновременно, разрешая их, как только они все завершатся, или сбой, как только один из них завершится с ошибкой. Я добавил эту логику в сервис благодаря @georgeawg. Здесь я переписываю этот код в службу и одновременно выполняю все вызовы:

services.factory('appData', function($http, $q) { 
    var appData = {}; 
    var coreData = {}; 

    appData.loadAppData = function(fetch) { 
     var deferred = $q.defer(); 
     var getUser = $q.defer(); 
     var getPricing = $q.defer(); 
     var getBilling = $q.defer(); 

     if (!fetch.user || coreData.currentUser) { 
     getUser.resolve(); 
     } else { 
     $http 
      .get('https://example.com/api/getUser') 
      .success(function(result){ 
      coreData.currentUser = result; 
      getUser.resolve(); 
      }).error(function(reason) { 
      getUser.reject(reason); 
      }); 
     } 

     if (!fetch.billing || coreData.billing) { 
     getBilling.resolve(); 
     } else { 
     $http 
      .get('https://example.com/api/getBilling') 
      .success(function(result) { 
      coreData.billing = result; 
      getBilling.resolve(); 
      }).error(function(reason) { 
      getBilling.reject(reason); 
      }); 
     } 

     if (!fetch.pricing || coreData.pricing) { 
     getPricing.resolve(); 
     } else { 
     $http 
      .get('https://example.com/api/getPricing') 
      .success(function(result) { 
      coreData.pricing = result; 
      getPricing.resolve(); 
      }).error(function(reason) { 
      getPricing.reject(reason); 
      }); 
     } 

     $q.all([getPricing.promise, getUser.promise, getBilling.promise]).then(function(result) { 
     deferred.resolve(coreData); 
     }, function(reason){ 
     deferred.reject(reason); 
     }); 

     return deferred.promise; 
    }; 

    return appData; 
    }); 
+1

Помните, что методы '.success' и' .error' устарели и были [удалены из AngularJS 1.6] (https://github.com/angular/angular.js/pull/15157). Также нет необходимости создавать обещание с '$ q.defer', поскольку служба $ http уже возвращает обещание. Для получения дополнительной информации см. [Является ли это «Отложенным Антипаттерном»?] (Http://stackoverflow.com/questions/30750207/is-this-a-deferred-antipattern) – georgeawg

Смежные вопросы