Cómo $ http llamada síncrona con AngularJS

132

¿Hay alguna forma de hacer una llamada sincrónica con AngularJS?

La documentación de AngularJS no es muy explícita o extensa para descubrir algunas cosas básicas.

EN UN SERVICIO:

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
fuente
Consulte también groups.google.com/d/topic/angular/qagzXXhS_VI/discussion para obtener algunas ideas sobre cómo lidiar con el comportamiento asincrónico: eventos, $ watch, precarga en el lado del servidor, use la promesa devuelta por $ http.
Mark Rajcok
1
Asíncrono siempre es mejor, especialmente cuando tienes promesas.
Andrew Joslin
Muchas veces, puede evitar llamadas sincrónicas. Vea cómo funciona $ resource stackoverflow.com/questions/11966252/… .
honzajde
3
@AndrewJoslin Asynchronous es peor cuando necesita una entrega ordenada.
Stijn Van Antwerpen

Respuestas:

113

No actualmente. Si observa el código fuente (a partir de este momento, octubre de 2012) , verá que la llamada a XHR abierto está realmente codificada para ser asíncrona (el tercer parámetro es verdadero):

 xhr.open(method, url, true);

Tendría que escribir su propio servicio que hiciera llamadas sincrónicas. En general, eso no es algo que normalmente querrá hacer debido a la naturaleza de la ejecución de JavaScript, terminará bloqueando todo lo demás.

... pero ... si realmente se desea bloquear todo lo demás, tal vez debería considerar las promesas y el servicio $ q . Le permite esperar hasta que se realice un conjunto de acciones asincrónicas, y luego ejecutar algo una vez que estén completas. No sé cuál es su caso de uso, pero vale la pena echarle un vistazo.

Fuera de eso, si va a rodar el suyo, puede encontrar más información sobre cómo hacer llamadas ajax síncronas y asíncronas aquí .

Espero que sea de ayuda.

Ben Lesh
fuente
12
¿Puede codificar un fragmento para lograr el uso del servicio $ q. Intenté muchas opciones pero está funcionando de manera asincrónica.
Venkat
1
Hay lugares en los que puede tener sentido, por ejemplo, justo cuando el usuario cierra el navegador (onbeforeunload), si desea guardar tiene que enviar una solicitud de sincronización, ¿otra opción es mostrar un cuadro de diálogo cancelar y luego reiniciar la ventana?
Braulio
2
@Venkat: Sé que esta es una respuesta tardía, pero como dije en la respuesta, la llamada siempre será "asincrónica", solo tiene que usar $ q para que espere la respuesta, luego continúe con su lógica dentro del .then(callback). algo así como: doSomething(); $http.get('/a/thing').then(doEverythingElse);.
Ben Lesh
3
El siguiente video me ayudó a estudiar las promesas AngularJS Promises con $ q
Ilya Palkin
1
@BenLesh No le agradezco el tiempo que invirtió, o el tiempo que alguien invierte. Soy libre de votar su respuesta y decir que me hubiera sido útil si me proporcionara un ejemplo. Vi su respuesta, no me ayudó, así que rechacé la votación y cerré la pestaña, y volví a google para tratar de encontrar una respuesta que me fuera más útil. No es el fin del mundo cuando alguien rechaza tu respuesta y te dice cómo podría mejorarse. ¿Hubiera preferido que votase en contra sin dejar un comentario sobre por qué? Solo siendo honesto.
circuitería
12

He trabajado con una fábrica integrada con Google Maps autocompletado y promesas hechas, espero que sirva.

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

solo necesita reemplazar el autocompleteService por esta solicitud con $ http incuida antes de la fábrica.

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

y $ http solicitud con

 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;
    });
 });

});

la pregunta en sí debe hacerse sobre:

deferred.resolve(varResult); 

cuando lo haya hecho bien y la solicitud:

deferred.reject(error); 

cuando hay un error, y luego:

return deferred.promise;
alelo
fuente
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
fuente
4

Recientemente me encontré con una situación en la que quería hacer llamadas $ http desencadenadas por una recarga de página. La solución con la que fui:

  1. Encapsula las dos llamadas en funciones
  2. Pase la segunda llamada $ http como devolución de llamada a la segunda función
  3. Llame a la segunda función en apon .success
vikas agartha
fuente
¿Qué pasa si es un bucle for, con n veces llamando al servidor?
mithun
2

Aquí hay una manera de hacerlo asincrónicamente y administrar las cosas como lo haría normalmente. Todo sigue siendo compartido. Obtiene una referencia al objeto que desea actualizar. Cada vez que actualiza eso en su servicio, se actualiza globalmente sin tener que mirar o devolver una promesa. Esto es realmente bueno porque puede actualizar el objeto subyacente desde el servicio sin tener que volver a vincular. Usando Angular de la manera en que debe usarse. Creo que probablemente sea una mala idea hacer que $ http.get / post sincronice. Obtendrá un retraso notable en el guión.

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();

Y en algún lugar de una vista:

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

Dado que la sincronización XHR está en desuso, es mejor no confiar en eso. Si necesita hacer una solicitud POST de sincronización, puede usar los siguientes ayudantes dentro de un servicio para simular una publicación de formulario.

Funciona creando un formulario con entradas ocultas que se publica en la URL especificada.

//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 según sea necesario para sus necesidades.

Adam Reis
fuente
-4

¿Qué hay de envolver su llamada en un Promise.all()método, es decir

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

De acuerdo con MDN

Promise.all espera todos los cumplimientos (o el primer rechazo)

Manjit Dosanjh
fuente
¿de qué estás hablando? la pregunta no tiene nada que ver con múltiples promesas ...
Ovidiu Dolha
¡Esperará a que se complete una o más promesas!
Manjit Dosanjh
¿Has usado esto para ver cómo funciona? Promise.all volverá otra promesa, no se transforma en llamadas asíncronas de sincronización
Ovidiu Dolha
Hmm ... parece que la documentación de MDN puede ser ambigua ... En realidad no ESPERA como se indica en su documentación.
Manjit Dosanjh
Bienvenido a SO. Lea este tutorial para obtener una respuesta de calidad.
era