Como $ http chamada síncrona com AngularJS

132

Existe alguma maneira de fazer uma chamada síncrona com o AngularJS?

A documentação do AngularJS não é muito explícita ou extensa para descobrir algumas coisas básicas.

EM UM SERVIÇO:

myService.getByID = function (id) {
    var retval = null;

    $http({
        url: "/CO/api/products/" + id,
        method: "GET"
    }).success(function (data, status, headers, config) {

        retval = data.Data;

    });

    return retval;
}
Flavio Oliveira
fonte
Consulte também groups.google.com/d/topic/angular/qagzXXhS_VI/discussion para obter algumas idéias sobre como lidar com o comportamento assíncrono: events, $ watch, pré-carregamento no servidor, use a promessa retornada de $ http.
26412 Mark Rajcok
1
Assíncrono é sempre melhor, especialmente quando você tem promessas.
Andrew Joslin
Muitas vezes, você pode evitar chamadas síncronas. Veja como o $ resource funciona stackoverflow.com/questions/11966252/… .
21813 honzajde
3
@AndrewJoslin Assíncrono é pior quando você precisa de entrega solicitada.
Stijn Van Antwerpen

Respostas:

113

Atualmente não. Se você olhar o código-fonte (a partir deste momento, em outubro de 2012) , verá que a chamada para o XHR open é realmente codificada para ser assíncrona (o terceiro parâmetro é verdadeiro):

 xhr.open(method, url, true);

Você precisaria escrever seu próprio serviço que fazia chamadas síncronas. Geralmente, isso não é algo que você normalmente desejará fazer, devido à natureza da execução do JavaScript, que acabará bloqueando todo o resto.

... mas .. se realmente desejar bloquear todo o resto, talvez você deva procurar promessas e o serviço $ q . Ele permite que você aguarde até que um conjunto de ações assíncronas seja concluído e execute algo quando todas estiverem concluídas. Não sei qual é o seu caso de uso, mas vale a pena dar uma olhada.

Fora isso, se você quiser criar suas próprias, mais informações sobre como fazer chamadas ajax síncronas e assíncronas podem ser encontradas aqui .

Espero que seja útil.

Ben Lesh
fonte
12
Você pode codificar o snippet para conseguir usar o serviço $ q. Eu tentei muitas opções, mas está funcionando de maneira assíncrona.
Venkat
1
Existem lugares onde pode fazer sentido, por exemplo, apenas quando o usuário fecha o navegador (antes da carga), se você deseja salvar, você precisa enviar uma solicitação de sincronização, outra opção é mostrar um cancelamento da caixa de diálogo e reiniciar o fechamento da janela?
Braulio
2
@Venkat: Eu sei que essa é uma resposta tardia, mas como eu disse na resposta, a chamada sempre será "assíncrona", você só precisa usar $ q para esperar pela resposta e continuar sua lógica dentro do .then(callback). algo como: doSomething(); $http.get('/a/thing').then(doEverythingElse);.
Ben Lesh
3
O vídeo a seguir me ajudou em estudar promessas AngularJS Promises com $ q
Ilya Palkin
1
@BenLesh Não sou insensível ao tempo que você dedica ou ao tempo que alguém dedica. Estou livre para votar sua resposta e dizer que teria sido útil para mim se um exemplo fosse fornecido. Vi sua resposta, não me ajudou, então votei e fechei a guia e voltei ao google para tentar encontrar uma resposta que fosse mais útil para mim. Não é o fim do mundo quando alguém baixa a sua resposta e diz como ela pode ser melhorada. Você preferiria que eu votasse menos sem deixar um comentário sobre o porquê? Apenas sendo honesto.
circuitos
12

Eu trabalhei com uma fábrica integrada com o Google Maps, preenchimento automático e promessas feitas, espero que você atenda.

http://jsfiddle.net/the_pianist2/vL9nkfe3/1/

você só precisa substituir o serviço de preenchimento automático por esta solicitação por $ http incuida antes da fábrica.

app.factory('Autocomplete', function($q, $http) {

e solicitação de $ http com

 var deferred = $q.defer();
 $http.get('urlExample').
success(function(data, status, headers, config) {
     deferred.resolve(data);
}).
error(function(data, status, headers, config) {
     deferred.reject(status);
});
 return deferred.promise;

<div ng-app="myApp">
  <div ng-controller="myController">
  <input type="text" ng-model="search"></input>
  <div class="bs-example">
     <table class="table" >
        <thead>
           <tr>
              <th>#</th>
              <th>Description</th>
           </tr>
        </thead>
        <tbody>
           <tr ng-repeat="direction in directions">
              <td>{{$index}}</td>
              <td>{{direction.description}}</td>
           </tr>
        </tbody>
     </table>
  </div>

'use strict';
 var app = angular.module('myApp', []);

  app.factory('Autocomplete', function($q) {
    var get = function(search) {
    var deferred = $q.defer();
    var autocompleteService = new google.maps.places.AutocompleteService();
    autocompleteService.getPlacePredictions({
        input: search,
        types: ['geocode'],
        componentRestrictions: {
            country: 'ES'
        }
    }, function(predictions, status) {
        if (status == google.maps.places.PlacesServiceStatus.OK) {
            deferred.resolve(predictions);
        } else {
            deferred.reject(status);
        }
    });
    return deferred.promise;
};

return {
    get: get
};
});

app.controller('myController', function($scope, Autocomplete) {
$scope.$watch('search', function(newValue, oldValue) {
    var promesa = Autocomplete.get(newValue);
    promesa.then(function(value) {
        $scope.directions = value;
    }, function(reason) {
        $scope.error = reason;
    });
 });

});

a própria pergunta deve ser feita sobre:

deferred.resolve(varResult); 

quando você tiver feito bem e a solicitação:

deferred.reject(error); 

quando houver um erro e, em seguida:

return deferred.promise;
allel
fonte
5
var EmployeeController = ["$scope", "EmployeeService",
        function ($scope, EmployeeService) {
            $scope.Employee = {};
            $scope.Save = function (Employee) {                
                if ($scope.EmployeeForm.$valid) {
                    EmployeeService
                        .Save(Employee)
                        .then(function (response) {
                            if (response.HasError) {
                                $scope.HasError = response.HasError;
                                $scope.ErrorMessage = response.ResponseMessage;
                            } else {

                            }
                        })
                        .catch(function (response) {

                        });
                }
            }
        }]


var EmployeeService = ["$http", "$q",
            function ($http, $q) {
                var self = this;

                self.Save = function (employee) {
                    var deferred = $q.defer();                
                    $http
                        .post("/api/EmployeeApi/Create", angular.toJson(employee))
                        .success(function (response, status, headers, config) {
                            deferred.resolve(response, status, headers, config);
                        })
                        .error(function (response, status, headers, config) {
                            deferred.reject(response, status, headers, config);
                        });

                    return deferred.promise;
                };
Srinivas
fonte
4

Recentemente, me deparei com uma situação em que eu queria fazer chamadas de $ http acionadas por um recarregamento de página. A solução que eu usei:

  1. Encapsular as duas chamadas em funções
  2. Passe a segunda chamada $ http como retorno de chamada para a segunda função
  3. Chame a segunda função em apon .success
Vikas Agartha
fonte
E se for um loop for, com n vezes chamando o servidor.
Mithun
2

Aqui está uma maneira de fazer isso de forma assíncrona e gerenciar coisas como faria normalmente. Tudo ainda é compartilhado. Você obtém uma referência ao objeto que deseja atualizar. Sempre que você atualiza isso em seu serviço, ele é atualizado globalmente sem ter que observar ou devolver uma promessa. Isso é muito bom, porque você pode atualizar o objeto subjacente de dentro do serviço sem precisar religar. Usando Angular da maneira como deve ser usado. Eu acho que é provavelmente uma má idéia tornar o $ http.get / post síncrono. Você receberá um atraso perceptível no script.

app.factory('AssessmentSettingsService', ['$http', function($http) {
    //assessment is what I want to keep updating
    var settings = { assessment: null };

    return {
        getSettings: function () {
             //return settings so I can keep updating assessment and the
             //reference to settings will stay in tact
             return settings;
        },
        updateAssessment: function () {
            $http.get('/assessment/api/get/' + scan.assessmentId).success(function(response) {
                //I don't have to return a thing.  I just set the object.
                settings.assessment = response;
            });
        }
    };
}]);

    ...
        controller: ['$scope', '$http', 'AssessmentSettingsService', function ($scope, as) {
            $scope.settings = as.getSettings();
            //Look.  I can even update after I've already grabbed the object
            as.updateAssessment();

E em algum lugar da vista:

<h1>{{settings.assessment.title}}</h1>
Bluebaron
fonte
0

Como a sincronização XHR está sendo preterida, é melhor não confiar nisso. Se você precisar fazer uma solicitação de sincronização POST, poderá usar os seguintes auxiliares dentro de um serviço para simular uma postagem de formulário.

Ele funciona criando um formulário com entradas ocultas, que é postado no URL especificado.

//Helper to create a hidden input
function createInput(name, value) {
  return angular
    .element('<input/>')
    .attr('type', 'hidden')
    .attr('name', name)
    .val(value);
}

//Post data
function post(url, data, params) {

    //Ensure data and params are an object
    data = data || {};
    params = params || {};

    //Serialize params
    const serialized = $httpParamSerializer(params);
    const query = serialized ? `?${serialized}` : '';

    //Create form
    const $form = angular
        .element('<form/>')
        .attr('action', `${url}${query}`)
        .attr('enctype', 'application/x-www-form-urlencoded')
        .attr('method', 'post');

    //Create hidden input data
    for (const key in data) {
        if (data.hasOwnProperty(key)) {
            const value = data[key];
            if (Array.isArray(value)) {
                for (const val of value) {
                    const $input = createInput(`${key}[]`, val);
                    $form.append($input);
                }
            }
            else {
                const $input = createInput(key, value);
                $form.append($input);
            }
        }
    }

    //Append form to body and submit
    angular.element(document).find('body').append($form);
    $form[0].submit();
    $form.remove();
}

Modifique conforme necessário para suas necessidades.

Adam Reis
fonte
-4

Que tal encerrar sua chamada em um Promise.all()método, ou seja

Promise.all([$http.get(url).then(function(result){....}, function(error){....}])

De acordo com a MDN

Promise.all aguarda todas as realizações (ou a primeira rejeição)

Manjit Dosanjh
fonte
do que você está falando? a questão não tem nada a ver com múltiplas promessas ...
Ovidiu Dolha
Esperará que uma ou mais promessas sejam concluídas!
Manjit Dosanjh
você usou isso para ver como funciona? Promise.all voltará outra promessa, ele não transforma assíncrona em chamadas de sincronização
Ovidiu Dolha
Hmm ... parece que a documentação do MDN pode ser ambígua ... Na verdade, NÃO ESPERA, conforme declarado na documentação.
Manjit Dosanjh
Bem-vindo ao SO. Leia este tutorial para fornecer respostas de qualidade.
thewaywewere