Pytanie Jak mogę zwrócić odpowiedź z połączenia asynchronicznego?


Mam funkcję foo który tworzy żądanie Ajax. Jak mogę zwrócić odpowiedź z foo?

Próbowałem zwrócić wartość z success wywołanie zwrotne, a także przypisanie odpowiedzi do zmiennej lokalnej w funkcji i zwrócenie jej, ale żaden z tych sposobów nie zwraca odpowiedzi.

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            result = response;
            // return response; // <- I tried that one as well
        }
    });

    return result;
}

var result = foo(); // It always ends up being `undefined`.

4321
2018-01-08 17:06


pochodzenie


@MaximShoustin Dodałem tylko odpowiedź JavaScript (bez jQuery), ale jak Felix powiedział - "Jak zwrócić odpowiedź z połączenia AJAX" jest naprawdę fajny sposób, aby powiedzieć "Jak działa współbieżność w JavaScript". Nie wspominając o Angular's $http jest bardzo podobny do jQuery $.ajax w każdym razie z niewielkimi różnicami (jak interoping z cyklem trawienia). - Benjamin Gruenbaum
Nie rób posługiwać się async: false. Zablokuje przeglądarkę i odstraszy użytkowników. Czytać blog.slaks.net/2015-01-04/async-method-patterns aby dowiedzieć się, jak pracować z operacjami asynchronicznymi. - SLaks
Jest to możliwe, tak jak Ty to zrobiłeś, poprzez adonc async: false jako atrybut pierwszego parametru. Zobacz moją odpowiedź poniżej. @tvanfosson dał to rozwiązanie w Stackoverflow. - arrowman
ta mey pomaga ci codingbin.com/get-return-data-ajax-call - Manoj Kumar
@SLaks Niech ktoś przekaże to na stronę "programistów rocka" na stronie AirAsia.com. To świetne studium przypadku na żywo (ulepszone przez wywołania ajaxów trwające kilka sekund), dlaczego nigdy nie używać async: false. - Mâtt Frëëman


Odpowiedzi:


-> Aby uzyskać bardziej ogólne wyjaśnienie asynchronicznego zachowania z różnymi przykładami, zobacz  Dlaczego zmienna jest niezmieniona po zmodyfikowaniu jej w funkcji? - Asynchroniczne odniesienie do kodu 

-> Jeśli już rozumiesz problem, przejdź do możliwych rozwiązań poniżej.

Problem

The ZA w Ajax oznacza asynchroniczny . Oznacza to, że wysłanie żądania (lub raczej otrzymanie odpowiedzi) jest wycofywane z normalnego przebiegu wykonywania. W twoim przykładzie $.ajax zwraca natychmiast i następne oświadczenie, return result;, jest wykonywane przed funkcją, którą przekazałeś jako success wywołano nawet wywołanie zwrotne.

Oto analogia, która - miejmy nadzieję - rozjaśni różnicę pomiędzy synchronicznym a asynchronicznym przepływem:

Synchroniczny

Wyobraź sobie, że dzwonisz do znajomego i prosisz go, żeby coś dla ciebie sprawdził. Chociaż może to chwilę potrwać, czekasz na telefon i wpatrujesz się w przestrzeń, dopóki twój przyjaciel nie udzieli ci odpowiedzi, której potrzebujesz.

To samo dzieje się, gdy wywołujesz funkcję zawierającą "normalny" kod:

function findItem() {
    var item;
    while(item_not_found) {
        // search
    }
    return item;
}

var item = findItem();

// Do something with item
doSomethingElse();

Nawet jeśli findItem może zająć dużo czasu, a potem pojawi się jakikolwiek kod var item = findItem(); musi czekać dopóki funkcja nie zwróci wyniku.

Asynchroniczny

Z tej samej przyczyny ponownie dzwonisz do swojego przyjaciela. Ale tym razem powiesz mu, że się spieszysz i powinien oddzwonię na twoim telefonie komórkowym. Odłóż słuchawkę, wyjdź z domu i rób to, co zaplanowałeś. Kiedy znajomy oddzwoni do ciebie, zajmiesz się informacjami, które ci przekazał.

Dokładnie to się dzieje, gdy wykonujesz żądanie Ajax.

findItem(function(item) {
    // Do something with item
});
doSomethingElse();

Zamiast czekać na odpowiedź, wykonywanie jest kontynuowane natychmiast i wykonywane jest polecenie po wywołaniu Ajax. Aby w końcu uzyskać odpowiedź, należy podać funkcję, która zostanie wywołana po otrzymaniu odpowiedzi, oddzwonić (zauważ coś? oddzwonić ?). Wszelkie instrukcje przychodzące po tym wywołaniu są wykonywane przed wywołaniem wywołania zwrotnego.


Rozwiązania)

Przyjmij asynchroniczną naturę JavaScript! Podczas gdy pewne operacje asynchroniczne zapewniają synchroniczne odpowiedniki (podobnie jak "Ajax"), generalnie zniechęca się ich używać, szczególnie w kontekście przeglądarki.

Dlaczego jest źle, pytasz?

JavaScript działa w wątku UI przeglądarki, a każdy długotrwały proces blokuje interfejs użytkownika, powodując, że nie odpowiada. Dodatkowo istnieje górny limit czasu wykonywania skryptów JavaScript, a przeglądarka zapyta użytkownika, czy kontynuować wykonanie, czy też nie.

Wszystko to jest naprawdę kiepskie dla użytkownika. Użytkownik nie będzie w stanie stwierdzić, czy wszystko działa poprawnie, czy nie. Co więcej, efekt będzie gorszy dla użytkowników z wolnym połączeniem.

W dalszej części przyjrzymy się trzem różnym rozwiązaniom, które opierają się na sobie:

  • Obietnice z async/await (ES2017 +, dostępny w starszych przeglądarkach, jeśli używasz transpilatora lub regeneratora)
  • Oddzwanianie (popularne w węźle)
  • Obietnice z then() (ES2015 +, dostępny w starszych przeglądarkach, jeśli używasz jednej z wielu obiecujących bibliotek)

Wszystkie trzy są dostępne w obecnych przeglądarkach i węźle 7+. 


ES2017 +: Obietnica z async/await

Wprowadzono nową wersję ECMAScript wydaną w 2017 roku wsparcie na poziomie składni dla funkcji asynchronicznych. Z pomocą async i await, możesz pisać asynchronicznie w "stylu synchronicznym". Nie popełniajcie jednak błędu: kod jest nadal asynchroniczny, ale łatwiej go odczytać / zrozumieć.

async/await opiera się na obietnicach: an async funkcja zawsze zwraca obietnicę. await "rozpakowuje" obietnicę i albo skutkuje wartością, przy której obietnica została rozwiązana, albo zgłasza błąd, jeśli obietnica została odrzucona.

Ważny: Możesz używać tylko await wewnątrz an async funkcjonować. Oznacza to, że na najwyższym poziomie nadal musisz pracować bezpośrednio z obietnicą.

Możesz przeczytać więcej na temat async i await na MDN.

Oto przykład, który opiera się na opóźnieniu powyżej:

// Using 'superagent' which will return a promise.
var superagent = require('superagent')

// This is isn't declared as `async` because it already returns a promise
function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}


async function getAllBooks() {
  try {
    // GET a list of book IDs of the current user
    var bookIDs = await superagent.get('/user/books');
    // wait for a second (just for the sake of this example)
    await delay(1000);
    // GET information about each book
    return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

// Async functions always return a promise
getAllBooks()
  .then(function(books) {
    console.log(books);
  });

Nowsza przeglądarka i węzeł obsługa wersji async/await. Możesz także wspierać starsze środowiska, przekształcając swój kod na ES5 przy pomocy regenerator (lub narzędzia, które używają regeneratora, takie jak Babel).


Pozwól funkcji akceptować callbacks

Callback to po prostu funkcja przekazywana do innej funkcji. Ta inna funkcja może wywołać funkcję przekazaną, gdy tylko będzie gotowa. W kontekście procesu asynchronicznego wywołanie zwrotne będzie wywoływane za każdym razem, gdy zostanie wykonany proces asynchroniczny. Zwykle wynik jest przekazywany do wywołania zwrotnego.

W przykładzie pytania możesz dokonać foo zaakceptuj wywołanie zwrotne i użyj go jako success oddzwonić. Więc to

var result = foo();
// Code that depends on 'result'

staje się

foo(function(result) {
    // Code that depends on 'result'
});

Tutaj zdefiniowaliśmy funkcję "inline", ale możesz przekazać dowolne odwołanie do funkcji:

function myCallback(result) {
    // Code that depends on 'result'
}

foo(myCallback);

foo sam jest zdefiniowany w następujący sposób:

function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}

callback będzie odnosić się do funkcji, do której przechodzimy foo kiedy to nazywamy i po prostu przekazujemy to dalej success. To znaczy. gdy żądanie Ajaxa się powiedzie, $.ajax zadzwonię callback i przekazuje odpowiedź na wywołanie zwrotne (do którego można się odwoływać) result, ponieważ tak zdefiniowaliśmy wywołanie zwrotne).

Możesz również przetworzyć odpowiedź przed przekazaniem jej do wywołania zwrotnego:

function foo(callback) {
    $.ajax({
        // ...
        success: function(response) {
            // For example, filter the response
            callback(filtered_response);
        }
    });
}

Łatwiej jest napisać kod za pomocą wywołań zwrotnych, niż mogłoby się wydawać. W końcu JavaScript w przeglądarce jest silnie sterowany zdarzeniami (zdarzenia DOM). Otrzymanie odpowiedzi Ajax to nic innego jak wydarzenie.
Trudności mogą pojawić się, gdy trzeba pracować z kodem stron trzecich, ale większość problemów można rozwiązać, po prostu zastanawiając się nad przebiegiem aplikacji.


ES2015 +: Obietnica z następnie()

The Promise API to nowa funkcja ECMAScript 6 (ES2015), ale jest dobra obsługa przeglądarki już. Istnieje również wiele bibliotek, które implementują standardowy interfejs Promises API i zapewniają dodatkowe metody ułatwiające korzystanie i kompozycję asynchronicznych funkcji (np. niebieski ptak).

Obietnice są pojemnikami dla przyszłość wartości. Kiedy obietnica otrzymuje wartość (jest zdecydowany) lub gdy zostanie anulowane (odrzucony), powiadamia wszystkich swoich "słuchaczy", którzy chcą uzyskać dostęp do tej wartości.

Zaletą prostego wywoływania zwrotnego jest to, że pozwalają oddzielić kod i łatwiej je skomponować.

Oto prosty przykład użycia obietnicy:

function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}

delay()
  .then(function(v) { // `delay` returns a promise
    console.log(v); // Log the value once it is resolved
  })
  .catch(function(v) {
    // Or do something else if it is rejected 
    // (it would not happen in this example, since `reject` is not called).
  });

Zastosowane do naszego połączenia Ajax mogliśmy użyć takich obietnic:

function ajax(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open('GET', url);
    xhr.send();
  });
}

ajax("/echo/json")
  .then(function(result) {
    // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });

Opisanie wszystkich zalet obiecującej oferty wykracza poza zakres tej odpowiedzi, ale jeśli napiszesz nowy kod, powinieneś poważnie je rozważyć. Zapewniają doskonałą abstrakcję i oddzielenie kodu.

Więcej informacji o obietnicach: Skały HTML5 - obietnice JavaScript

Uwaga boczna: odroczone obiekty jQuery

Odroczone obiekty są niestandardową realizacją obietnic jQuery (zanim standaryzacja Promise API została znormalizowana). Zachowują się prawie jak obietnice, ale eksponują nieco inny interfejs API.

Każda metoda jQuery w Ajaksie zwraca już "obiekt odroczony" (w rzeczywistości obietnicę odroczonego obiektu), którą możesz po prostu zwrócić ze swojej funkcji:

function ajax() {
    return $.ajax(...);
}

ajax().done(function(result) {
    // Code depending on result
}).fail(function() {
    // An error occurred
});

Nota boczna: Promise gotchas

Pamiętaj, że obietnice i odroczone obiekty są po prostu pojemniki dla przyszłej wartości nie są samą wartością. Załóżmy na przykład, że masz następujące:

function checkPassword() {
    return $.ajax({
        url: '/password',
        data: {
            username: $('#username').val(),
            password: $('#password').val()
        },
        type: 'POST',
        dataType: 'json'
    });
}

if (checkPassword()) {
    // Tell the user they're logged in
}

Ten kod błędnie interpretuje powyższe problemy asynchroniczne. Konkretnie, $.ajax() nie blokuje kodu podczas sprawdzania strony "/ password" na serwerze - wysyła żądanie do serwera i gdy czeka, natychmiast zwraca obiekt odroczony jQuery Ajax, a nie odpowiedź z serwera. Oznacza to if oświadczenie będzie zawsze otrzymywało obiekt Odroczony, traktuj to jako truei postępuj tak, jakby użytkownik był zalogowany. Nie jest dobry.

Ale poprawka jest łatwa:

checkPassword()
.done(function(r) {
    if (r) {
        // Tell the user they're logged in
    } else {
        // Tell the user their password was bad
    }
})
.fail(function(x) {
    // Tell the user something bad happened
});

Niezalecane: połączenia synchroniczne "Ajax"

Jak już wspomniałem, niektóre (!) Operacje asynchroniczne mają synchroniczne odpowiedniki. Nie opowiadam się za ich użyciem, ale dla kompletności, oto jak wykonać połączenie synchroniczne:

Bez jQuery

Jeśli używasz bezpośrednio a XMLHTTPRequest obiekt, zdać false jako trzeci argument .open.

jQuery

Jeśli użyjesz jQuery, możesz ustawić async opcja do false. Zauważ, że ta opcja jest przestarzałe od jQuery 1.8. Możesz wtedy nadal używać a success oddzwonienie lub dostęp do responseText własność Obiekt jqXHR:

function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}

Jeśli używasz dowolnej innej metody Ajax jQuery, takiej jak $.get, $.getJSONitp., musisz to zmienić na $.ajax(ponieważ możesz przekazać parametry konfiguracyjne tylko do $.ajax).

Heads-up! Nie można wykonać synchronizacji JSONP żądanie. JSONP z natury jest zawsze asynchroniczny (jeszcze jeden powód, aby nie brać pod uwagę tej opcji).


4666
2018-01-08 17:06



@Pommy: Jeśli chcesz korzystać z jQuery, musisz to uwzględnić. Należy zapoznać się docs.jquery.com/Tutorials:Getting_Started_with_jQuery. - Felix Kling
W rozwiązaniu 1, pod jQuery, nie mogłem zrozumieć tej linii: If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax. (Tak, zdaję sobie sprawę, że mój nick jest w tym przypadku odrobinę ironiczny) - gibberish
@Gibberish: Mmmh, nie wiem, jak można to uczynić jaśniejszym. Czy widzisz, jak foo jest wywoływana i przekazywana jest do niego funkcja (foo(function(result) {....});)? result jest używane wewnątrz tej funkcji i jest odpowiedzią na żądanie Ajax. Aby odwołać się do tej funkcji, wywoływany jest pierwszy parametr foo callback i przypisane do success zamiast anonimowej funkcji. Więc, $.ajax zadzwonię callback kiedy prośba się powiodła. Próbowałem wyjaśnić to nieco więcej. - Felix Kling
Czat na to pytanie jest martwy, więc nie jestem pewien, gdzie proponować zarysowane zmiany, ale proponuję: 1) Zmień synchroniczną część na prostą dyskusję, dlaczego jest źle, bez przykładowego kodu, jak to zrobić. 2) Usuń / scalaj przykłady wywołań zwrotnych, aby pokazać tylko bardziej elastyczne podejście odroczone, co moim zdaniem może być nieco łatwiejsze do zrozumienia dla tych uczących się Javascript. - Chris Moschini
@Jessi: Myślę, że źle zrozumiałeś tę część odpowiedzi. Nie możesz użyć $.getJSON jeśli chcesz, aby żądanie Ajax było synchroniczne. Jednak nie powinieneś chcieć, aby żądanie było synchroniczne, więc to nie ma zastosowania. Powinieneś używać wywołań zwrotnych lub obietnic do obsłużenia odpowiedzi, jak wyjaśniono wcześniej w odpowiedzi. - Felix Kling


Jeśli jesteś nie używając jQuery w kodzie, ta odpowiedź jest dla ciebie

Twój kod powinien być czymś podobnym do tego:

function foo() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
    return httpRequest.responseText;
}

var result = foo(); // always ends up being 'undefined'

Felix Kling wykonał świetną robotę, pisząc odpowiedź dla osób używających jQuery dla AJAX, postanowiłem zapewnić alternatywę dla osób, które nie są.

(Uwaga, dla osób korzystających z nowego fetch API, Angular lub obietnice Dodałem kolejną odpowiedź poniżej)


Przed czym stoisz

To jest krótkie podsumowanie "Wyjaśnienie problemu" z drugiej odpowiedzi, jeśli nie jesteś pewien po przeczytaniu tego, przeczytaj to.

The ZA w AJAX oznacza asynchroniczny. Oznacza to, że wysłanie żądania (lub raczej otrzymanie odpowiedzi) jest wycofywane z normalnego przebiegu wykonywania. W twoim przykładzie .send zwraca natychmiast i następne oświadczenie, return result;, jest wykonywane przed funkcją, którą przekazałeś jako success wywołano nawet wywołanie zwrotne.

Oznacza to, że po powrocie zdefiniowany detektor nie został jeszcze wykonany, co oznacza, że ​​zwracana wartość nie została zdefiniowana.

Oto prosta analogia

function getFive(){ 
    var a;
    setTimeout(function(){
         a=5;
    },10);
    return a;
}

(Skrzypce)

Wartość a zwrócony jest undefined od czasu a=5 część jeszcze nie została wykonana. AJAX działa w ten sposób, zwracasz wartość, zanim serwer uzyskał szansę poinformowania przeglądarki, jaka jest ta wartość.

Jednym z możliwych rozwiązań tego problemu jest kodowanie reaktywnie , mówiąc programowi, co należy zrobić po zakończeniu obliczeń.

function onComplete(a){ // When the code completes, do this
    alert(a);
}

function getFive(whenDone){ 
    var a;
    setTimeout(function(){
         a=5;
         whenDone(a);
    },10);
}

To się nazywa CPS. Zasadniczo, mijamy getFive czynność do wykonania po jej zakończeniu, mówimy naszemu kodowi, jak reagować po zakończeniu zdarzenia (np. nasze wywołanie AJAX lub w tym przypadku limit czasu).

Używanie będzie:

getFive(onComplete);

Który powinien ostrzec "5" na ekranie. (Skrzypce).

Możliwe rozwiązania

Zasadniczo istnieją dwa sposoby rozwiązania tego problemu:

  1. Spraw, aby wywołanie AJAX było synchroniczne (możesz nazwać to SJAX).
  2. Zmodyfikuj kod, aby działał poprawnie z oddzwanianiem.

1. Synchroniczna AJAX - Nie rób tego !!

Jeśli chodzi o synchroniczną AJAX, nie rób tego! Odpowiedź Felixa podnosi pewne przekonujące argumenty na temat tego, dlaczego to zły pomysł. Podsumowując, zamrozi przeglądarkę użytkownika, dopóki serwer nie zwróci odpowiedzi i utworzy bardzo kiepski interfejs użytkownika. Oto kolejne krótkie podsumowanie z MDN na temat:

XMLHttpRequest obsługuje komunikację synchroniczną i asynchroniczną. Zasadniczo jednak żądania asynchroniczne powinny być preferowane względem żądań synchronicznych ze względu na wydajność.

Krótko mówiąc, żądania synchroniczne blokują wykonanie kodu ... ... może to spowodować poważne problemy ...

Jeśli ty mieć aby to zrobić, możesz przekazać flagę: Oto jak:

var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
request.send(null);

if (request.status === 200) {// That's HTTP for 'ok'
  console.log(request.responseText);
}

2. Kod restrukturyzacji

Pozwól swojej funkcji zaakceptować wywołanie zwrotne. W przykładowym kodzie foo można przyjąć akceptację oddzwaniania. Powiemy naszemu kodowi, jak to zrobić reagować gdy foo kończy.

Więc:

var result = foo();
// code that depends on `result` goes here

Staje się:

foo(function(result) {
    // code that depends on `result`
});

Tutaj przekazaliśmy anonimową funkcję, ale równie dobrze możemy przekazać odwołanie do istniejącej funkcji, dzięki czemu wygląda ona tak:

function myHandler(result) {
    // code that depends on `result`
}
foo(myHandler);

Aby uzyskać więcej informacji na temat tego, jak ten projekt wywołania zwrotnego jest wykonywany, sprawdź odpowiedź Felixa.

Zdefiniujmy teraz samego foo, aby działał odpowiednio

function foo(callback) {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onload = function(){ // when the request is loaded
       callback(httpRequest.responseText);// we're calling our method
    };
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
}

(skrzypce)

Uczyniliśmy teraz, że nasza funkcja foo akceptuje akcję, która ma zostać uruchomiona po pomyślnym zakończeniu AJAX, możemy rozszerzyć to dalej, sprawdzając, czy status odpowiedzi nie jest równy 200 i działając zgodnie z tym (utwórz program obsługi awarii itp.). Skutecznie rozwiązujemy nasz problem.

Jeśli nadal trudno to zrozumieć przeczytaj przewodnik wprowadzający do AJAX w MDN.


888
2018-05-29 23:30



"żądania synchroniczne blokują wykonywanie kodu i mogą przeciekać pamięć i zdarzenia" w jaki sposób synchroniczne żądanie może wyciekać z pamięci? - Matthew G
@MatthewG Dodałem do niego nagrodę za bounty to pytanieZobaczę, co mogę wyłowić. Usuwam cytat z odpowiedzi w międzyczasie. - Benjamin Gruenbaum
Tylko dla odniesienia, XHR 2 pozwala nam używać onload handler, który uruchamia się tylko wtedy, gdy readyState jest 4. Oczywiście nie jest to obsługiwane w IE8. (iirc, może potrzebować potwierdzenia.) - Florian Margaine
Wyjaśnienie, w jaki sposób przekazać anonimową funkcję jako oddzwonienie, jest poprawne, ale wprowadza w błąd. Przykład var bar = foo (); prosi o zdefiniowanie zmiennej, podczas gdy twój sugerowany foo (functim () {}); nie definiuje paska - Robbie Averill
@BenjaminGruenbaum Jak mogę zwrócić zawartość result w zmiennej, której mogę używać gdzieś indziej w moim kodzie, zamiast drukować ją za pomocą HTML? - MorganFR


XMLHttpRequest 2 (przede wszystkim przeczytaj odpowiedzi Benjamina Gruenbauma i Felixa Klinga)

Jeśli nie korzystasz z jQuery i chcesz mieć niezły krótki XMLHttpRequest 2, który działa w nowoczesnych przeglądarkach, a także w przeglądarkach mobilnych, sugeruję używać go w ten sposób:

function ajax(a, b, c){ // URL, callback, just a placeholder
  c = new XMLHttpRequest;
  c.open('GET', a);
  c.onload = b;
  c.send()
}

Jak widzisz:

  1. Jest krótszy niż wszystkie inne funkcje wymienione.
  2. Callback ustawia się bezpośrednio (więc bez dodatkowych niepotrzebnych zamknięć).
  3. Używa nowego onload (nie musisz więc sprawdzać statusu readystate &&)
  4. Są inne sytuacje, których nie pamiętam, powodujące irytujące XMLHttpRequest 1.

Istnieją dwa sposoby uzyskania odpowiedzi na to wywołanie Ajax (trzy za pomocą nazwy var XMLHttpRequest):

Najprostszy:

this.response

Lub jeśli z jakiegoś powodu ty bind() wywołanie zwrotne do klasy:

e.target.response

Przykład:

function callback(e){
  console.log(this.response);
}
ajax('URL', callback);

Lub (powyższe jest lepsze anonimowe funkcje zawsze stanowią problem):

ajax('URL', function(e){console.log(this.response)});

Nic prostszego.

Teraz niektórzy ludzie prawdopodobnie powiedzą, że lepiej jest użyć onreadystatechange lub nawet nazwy zmiennej XMLHttpRequest. To jest źle.

Sprawdzić Zaawansowane funkcje XMLHttpRequest

Obsługiwany we wszystkich * nowoczesnych przeglądarkach. I mogę potwierdzić, ponieważ używam tego podejścia, ponieważ istnieje XMLHttpRequest 2. Nigdy nie miałem żadnego problemu ze wszystkimi używanymi przeglądarkami.

onreadystatechange jest przydatny tylko wtedy, gdy chcesz uzyskać nagłówki w stanie 2.

Używając XMLHttpRequest nazwa zmiennej jest kolejnym dużym błędem, ponieważ musisz wykonać wywołanie zwrotne wewnątrz zamknięć onloadystatechange, jeśli jeszcze go nie zgubiłeś.


Teraz, jeśli chcesz czegoś bardziej skomplikowanego za pomocą posta i formularza, możesz łatwo rozszerzyć tę funkcję:

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.send(d||null)
}

Znowu ... jest to bardzo krótka funkcja, ale pojawia się i publikuje.

Przykłady użycia:

x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data

Lub przeprowadź pełny element formularza (document.getElementsByTagName('form')[0]):

var fd = new FormData(form);
x(url, callback, 'post', fd);

Lub ustaw niektóre niestandardowe wartości:

var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);

Jak widzisz, nie zaimplementowałem synchronizacji ... to źle.

Powiedziawszy to ... dlaczego nie robisz tego w łatwy sposób?


Jak wspomniano w komentarzu, użycie błędu i synchronizacji całkowicie przełamuje punkt odpowiedzi. Jaki jest dobry sposób na właściwe wykorzystanie Ajax?

Obsługa błędów

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.onerror = error;
  c.send(d||null)
}

function error(e){
  console.log('--Error--', this.type);
  console.log('this: ', this);
  console.log('Event: ', e)
}
function displayAjax(e){
  console.log(e, this);
}
x('WRONGURL', displayAjax);

W powyższym skrypcie masz obsługę błędów, która jest statycznie zdefiniowana, więc nie zagraża tej funkcji. Procedura obsługi błędów może być również używana do innych funkcji.

Ale naprawdę wydostać się z błędu tylko sposobem jest napisanie niewłaściwego adresu URL, w którym to przypadku każda przeglądarka zgłasza błąd.

Procedury obsługi błędów mogą być przydatne, jeśli ustawisz niestandardowe nagłówki, ustaw parametr responseType na bufor tablicy obiektów blob itp.

Nawet jeśli przekażesz "POSTAPAPAP" jako metodę, nie spowoduje to błędu.

Nawet jeśli przekazujesz "fdggdgilfdghfldj" jako formdata, nie spowoduje to błędu.

W pierwszym przypadku błąd znajduje się wewnątrz displayAjax() pod this.statusText tak jak Method not Allowed.

W drugim przypadku po prostu działa. Musisz sprawdzić po stronie serwera, czy przekazałeś właściwe dane pocztowe.

domena niedozwolona automatycznie generuje błąd.

W odpowiedzi na błąd nie ma kodów błędów.

Jest tylko this.type który jest ustawiony na błąd.

Po co dodawać obsługę błędów, jeśli nie masz całkowitej kontroli nad błędami? Większość błędów jest zwracana wewnątrz tej funkcji wywołania zwrotnego displayAjax().

Tak więc: nie musisz sprawdzać błędów, jeśli możesz poprawnie skopiować i wkleić adres URL. ;)

PS: Jako pierwszy test napisałem x ('x', displayAjax) ... i otrzymałem odpowiedź ... ??? Sprawdziłem więc folder, w którym znajduje się kod HTML, oraz plik o nazwie "x.xml". Więc nawet jeśli zapomnisz rozszerzenie pliku XMLHttpRequest 2 ZNAJDZIESZ GO. I LOL'd


Przeczytaj plik synchroniczny

Nie rób tego.

Jeśli chcesz zablokować przeglądarkę przez jakiś czas, ładuj ładnie duży plik txt synchronicznie.

function omg(a, c){ // URL
  c = new XMLHttpRequest;
  c.open('GET', a, true);
  c.send();
  return c; // Or c.response
}

Teraz możesz zrobić

 var res = omg('thisIsGonnaBlockThePage.txt');

Nie ma innego sposobu, aby to zrobić w sposób nie asynchroniczny. (Tak, z pętlą setTimeout ... ale poważnie?)

Inną kwestią jest ... jeśli pracujesz z API lub po prostu masz własne pliki listy lub cokolwiek, zawsze używasz różnych funkcji dla każdego żądania ...

Tylko jeśli masz stronę, na której ładujesz zawsze ten sam XML / JSON lub cokolwiek potrzebujesz tylko jednej funkcji. W takim przypadku zmodyfikuj trochę funkcję Ajax i zastąp b specjalną funkcją.


Powyższe funkcje są przeznaczone do podstawowego użytku.

Jeśli chcesz ROZSZERZĆ funkcję ...

Tak, możesz.

Używam wielu API i jedną z pierwszych funkcji, które integruję na każdej stronie HTML jest pierwsza funkcja Ajax w tej odpowiedzi, z GET tylko ...

Ale możesz zrobić wiele rzeczy za pomocą XMLHttpRequest 2:

Zrobiłem menedżera pobierania (używając zakresów po obu stronach z CV, filereader, systemu plików), różnych konwerterów resizerów obrazu przy użyciu płótna, zapełniam bazy danych websql bazami base64images i wiele więcej ... Ale w takich przypadkach powinieneś stworzyć funkcję tylko w tym celu ... czasami potrzebujesz blobu, buforów tablicowych, możesz ustawić nagłówki, nadpisać typ MIME i jest o wiele więcej ...

Ale tutaj jest pytanie, jak zwrócić odpowiedź Ajax ... (dodałem w łatwy sposób.)


304
2017-08-19 08:06



Podczas gdy ta odpowiedź jest dobra (i wszyscy miłość XHR2 i publikowanie danych plików i danych wieloczęściowych jest całkowicie niesamowite) - pokazuje to cukier syntaktyczny do umieszczania XHR z JavaScriptem - możesz umieścić to w poście na blogu (lubię) lub nawet w bibliotece (nie jestem pewien co do tego Nazwa x, ajax lub xhr może być ładniej :)). Nie widzę, w jaki sposób rozwiązuje problem zwracania odpowiedzi z wywołania AJAX. (Ktoś jeszcze mógł to zrobić var res = x("url") i nie rozumiem, dlaczego to nie działa;)). Na marginesie - byłoby fajnie, gdybyś wrócił c z metody, dzięki której użytkownicy mogą się podłączyć error itp. - Benjamin Gruenbaum
2.ajax is meant to be async.. so NO var res=x('url').. Oto cały punkt tego pytania i odpowiedzi :) - Benjamin Gruenbaum
dlaczego w funkcjach jest parametr "c", jeśli w pierwszym wierszu nadpisujesz jakąś wartość? czy czegoś brakuje? - Brian H.
Możesz użyć parametrów jako symbolu zastępczego, aby uniknąć wielokrotnego zapisywania "var" - cocco
@Cocco Więc napisałeś mylący, nieczytelny kod w SO odpowiedź w celu zapisania kilku naciśnięć klawiszy? Proszę tego nie robić. - stone


Jeśli używasz obietnic, ta odpowiedź jest dla Ciebie.

Oznacza to AngularJS, jQuery (z odroczonym), natywny zamiennik XHR (pobieranie), EmberJS, BackboneJS save lub dowolną bibliotekę węzłów, która zwraca obietnice.

Twój kod powinien być czymś podobnym do tego:

function foo() {
    var data;
    // or $.get(...).then, or request(...).then, or query(...).then
    fetch("/echo/json").then(function(response){
        data = response.json();
    });
    return data;
}

var result = foo(); // result is always undefined no matter what.

Felix Kling wykonał świetną robotę, pisząc odpowiedź dla osób używających jQuery z wywołaniami zwrotnymi dla AJAX. Mam odpowiedź dla rodzimego XHR. Ta odpowiedź dotyczy ogólnego użycia obietnic na frontendach lub w backendach.


Główny problem

Model współbieżności JavaScript w przeglądarce i na serwerze z NodeJS / io.js jest asynchroniczny i reaktywny.

Kiedykolwiek wywołasz metodę, która zwraca obietnicę, then obsługują zawsze wykonywane asynchronicznie - czyli po kod pod nimi, którego nie ma w .then treser.

Oznacza to, że wracasz data  then zdefiniowany program obsługi jeszcze nie został wykonany. To z kolei oznacza, że ​​zwracana wartość nie została ustawiona na właściwą wartość w czasie.

Oto prosta analogia do problemu:

    function getFive(){
        var data;
        setTimeout(function(){ // set a timer for one second in the future
           data = 5; // after a second, do this
        }, 1000);
        return data;
    }
    document.body.innerHTML = getFive(); // `undefined` here and not 5

Wartość data jest undefined od czasu data = 5 część jeszcze nie została wykonana. Prawdopodobnie wykona się w ciągu sekundy, ale do tego czasu nie ma znaczenia dla zwróconej wartości.

Ponieważ operacja jeszcze się nie wydarzyła (AJAX, wywołanie serwera, IO, timer), zwracasz wartość, zanim żądanie dostało szansę poinformowania twojego kodu, jaka jest ta wartość.

Jednym z możliwych rozwiązań tego problemu jest kodowanie reaktywnie , mówiąc programowi, co należy zrobić po zakończeniu obliczeń. Obietnice aktywnie umożliwiają to poprzez bycie z natury czasowej (czasowej).

Szybko podsumuj obietnice

Obietnica to wartość w czasie. Obietnice mają stan, zaczynają jako oczekujące bez wartości i mogą rozstrzygać:

  • spełniony oznacza, że ​​obliczenia zakończyły się pomyślnie.
  • odrzucony oznacza to, że obliczenia nie powiodły się.

Obietnica może zmienić tylko stany pewnego razu po czym zawsze pozostanie w tym samym stanie. Możesz dołączyć then opiekunów do obietnic wydobyć ich wartość i obsługi błędów. then obsługujący pozwalają łańcuchowanie połączeń. Obietnice są tworzone przez za pomocą interfejsów API, które je zwracają. Na przykład bardziej nowoczesny zamiennik AJAX fetch lub jQuery $.get obietnice zwrotu.

Kiedy dzwonimy .then na obietnicy i powrót coś z tego - dostajemy obietnicę przetwarzana wartość. Jeśli zwrócimy kolejną obietnicę, dostaniemy niesamowite rzeczy, ale trzymajmy nasze konie.

Z obietnicami

Zobaczmy, jak możemy rozwiązać powyższy problem z obietnicami. Po pierwsze, pokażmy nasze zrozumienie obietnic stany z góry za pomocą Obiecuj konstruktora do tworzenia funkcji opóźnienia:

function delay(ms){ // takes amount of milliseconds
    // returns a new promise
    return new Promise(function(resolve, reject){
        setTimeout(function(){ // when the time is up
            resolve(); // change the promise to the fulfilled state
        }, ms);
    });
}

Teraz, po przekonwertowaniu setTimeout na obietnice, możemy użyć then aby to policzyć:

function delay(ms){ // takes amount of milliseconds
  // returns a new promise
  return new Promise(function(resolve, reject){
    setTimeout(function(){ // when the time is up
      resolve(); // change the promise to the fulfilled state
    }, ms);
  });
}

function getFive(){
  // we're RETURNING the promise, remember, a promise is a wrapper over our value
  return delay(100).then(function(){ // when the promise is ready
      return 5; // return the value 5, promises are all about return values
  })
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){ 
   document.body.innerHTML = five;
});

Zasadniczo zamiast zwracać wartość czego nie możemy zrobić ze względu na model współbieżności - zwracamy obwoluta dla wartości, którą możemy rozwijać z then. To jest jak pudełko, z którym możesz się otworzyć then.

Zastosowanie tego

To samo dotyczy oryginalnego wywołania interfejsu API:

function foo() {
    // RETURN the promise
    return fetch("/echo/json").then(function(response){
        return response.json(); // process it inside the `then`
    });
}

foo().then(function(response){
    // access the value inside the `then`
})

Więc to działa równie dobrze. Nauczyliśmy się, że nie możemy zwracać wartości z już asynchronicznych wywołań, ale możemy użyć obietnic i połączyć je, aby wykonać przetwarzanie. Teraz wiemy, jak zwrócić odpowiedź z wywołania asynchronicznego.

ES2015 (ES6)

ES6 wprowadza generatory które są funkcjami, które mogą wrócić w środku, a następnie wznowić punkt, w którym były. Jest to zazwyczaj przydatne w przypadku sekwencji, na przykład:

function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
    yield 1;
    yield 2;
    while(true) yield 3;
}

Jest funkcją, która zwraca iterator nad sekwencją 1,2,3,3,3,3,.... które można iterować. Chociaż jest to interesujące samo w sobie i otwiera przestrzeń dla wielu możliwości, istnieje jeden szczególnie interesujący przypadek.

Jeśli sekwencja, którą tworzymy, jest sekwencją czynności, a nie liczbami - możemy wstrzymać tę funkcję za każdym razem, gdy zostanie wykonana akcja i czekać na nią, zanim wznowimy działanie. Zamiast sekwencji liczb potrzebujemy sekwencji przyszłość wartości - to znaczy: obietnice.

Ta nieco trudna, ale bardzo mocna sztuczka pozwala nam pisać synchronicznie kod asynchroniczny. Jest kilka "biegaczy", którzy robią to za Ciebie, napisanie jednego jest krótkim kodem kodu, ale jest poza zakresem tej odpowiedzi. Będę używał Bluebirda Promise.coroutine tutaj, ale są też inne opakowania co lub Q.async.

var foo = coroutine(function*(){
    var data = yield fetch("/echo/json"); // notice the yield
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
});

Ta metoda zwraca samą obietnicę, którą możemy spożyć od innych partnerów. Na przykład:

var main = coroutine(function*(){
   var bar = yield foo(); // wait our earlier coroutine, it returns a promise
   // server call done here, code below executes when done
   var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
   console.log(baz); // runs after both requests done
});
main();

ES2016 (ES7)

W ES7 jest to dalej standaryzowane, teraz jest kilka propozycji, ale we wszystkich z nich możesz await obietnica. To jest po prostu "cukier" (ładniejsza składnia) dla powyższej propozycji ES6, dodając async i await słowa kluczowe. Podejmując powyższy przykład:

async function foo(){
    var data = await fetch("/echo/json"); // notice the await
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
}

Wciąż zwraca obietnicę tak samo :)


245
2018-05-12 02:22



ES 2016 wygląda na ostateczne rozwiązanie tego problemu raz na zawsze. - ShrekOverflow
Absolutnie ostateczne rozwiązanie, raz na zawsze? A co z całkowicie absolutnie ostatecznym rozwiązaniem, raz na zawsze plus jeden? - Shmiddty
skąd do cholery dostajesz "piątkę"? nigdy nie jest deklarowane gdzie. - recurf
@recurf przede wszystkim "gdzie do cholery" prawie nie pasuje do tej strony (dzieci też z niej korzystają). Po drugie - jest to parametr funkcyjny, mógłbym go nazwać gdziekolwiek indziej i działałby równie dobrze. - Benjamin Gruenbaum
Czy odpowiedziałeś na pytanie? Jak mogę zwrócić odpowiedź z połączenia asynchronicznego? @BenjaminGruenbaum - OnPrinciples


Używasz Ajax niepoprawnie. Nie chodzi o to, aby zwracać cokolwiek, ale zamiast tego przekazywać dane do czegoś zwanego funkcją zwrotną, która obsługuje dane.

To jest:

function handleData( responseData ) {

    // Do what you want with the data
    console.log(responseData);
}

$.ajax({
    url: "hi.php",
    ...
    success: function ( data, status, XHR ) {
        handleData(data);
    }
});

Zwrócenie niczego w module obsługi przesyłania nie spowoduje żadnego działania. Zamiast tego musisz przekazać dane lub zrobić to, co chcesz, bezpośrednio w funkcji sukcesu.


193
2018-05-23 02:05



Ta odpowiedź jest całkowicie semantyczna ... Twoja metoda sukcesu to tylko wywołanie zwrotne w ramach wywołania zwrotnego. Mogłeś po prostu mieć success: handleData i to by działało. - Jacques
A co jeśli chcesz zwrócić "responseData" poza "handleData" ... :) ... jak to zrobisz ...? ... ponieważ prosty powrót spowoduje powrót do "powodzenia" wywołania ajax ... a nie poza "handleData" ... - pesho hristov
@ Jacques & @pesho hristov Tęskniłeś za tym punktem. Wyślij handler nie jest success metoda, to otaczający zakres $.ajax. - travnik
@travnik Nie tęskniłem za tym. Gdybyś wziął zawartość handleData i umieścił ją w metodzie sukcesu, działałby dokładnie tak samo ... - Jacques


Najprostszym rozwiązaniem jest utworzenie funkcji JavaScript i wywołanie jej dla Ajax success oddzwonić.

function callServerAsync(){
    $.ajax({
        url: '...',
        success: function(response) {

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}

function foo(callback) {

    $.ajax({
        url: '...',
        success: function(response) {
           return callback(null, response);
        }
    });
}

var result = foo(function(err, result){
          if (!err)
           console.log(result);    
}); 

185
2018-02-18 18:58



Nie wiem, kto głosował negatywnie. Ale to jest praca, która sprawdziła się i użyłem tego podejścia do stworzenia całej aplikacji. Jquery.ajax nie zwraca danych, więc lepiej użyć powyższego podejścia. Jeśli jest źle, wyjaśnij i zaproponuj lepszy sposób na zrobienie tego. - Hemant Bavle
Przepraszam, zapomniałem zostawić komentarz (zwykle robię!). Zignorowałem to. Downvotes nie wskazują na faktyczną poprawność lub brak, wskazują na przydatność w kontekście lub brak. Nie wydaje mi się, aby twoja odpowiedź była przydatna, biorąc pod uwagę Felixa, która wyjaśnia to już tylko znacznie bardziej szczegółowo. Na marginesie, dlaczego miałbyś uszeregować odpowiedź, jeśli jest to JSON? - Benjamin Gruenbaum
ok .. @Benjamin użyłem stringify, aby przekonwertować obiekt JSON na ciąg. I dzięki za wyjaśnienie twojego punktu. Będziemy pamiętać o dodaniu bardziej wyszukanych odpowiedzi. - Hemant Bavle
A co jeśli chcesz zwrócić "responseObj" poza "successCallback" ... :) ... jak to zrobisz ...? ... ponieważ prosty powrót spowoduje powrót do "powodzenia" wywołania ajax ... a nie poza "successCallback" ... - pesho hristov


Odpowiem z okropnie wyglądającym, ręcznie rysowanym komiksem. Drugi obraz jest powodem result jest undefined w twoim przykładzie kodu.

enter image description here


155
2017-08-11 14:17



Obraz jest wart tysiąca słów, Osoba A - Postaraj się o szczegóły B osoby, aby naprawić swój samochód Osoba B - Wykonuje Ajax Call i czeka na odpowiedź z serwera w celu ustalenia szczegółów naprawy samochodu, po otrzymaniu odpowiedzi, funkcja Ajax Success wywołuje funkcję Person B i przekazuje odpowiedź jako argument, Osoba A otrzymuje odpowiedź. - stom
Byłoby wspaniale, gdyby dodać linie kodu z każdym obrazem, aby zilustrować pojęcia. - Hassan Baig
Najlepsze wyjaśnienie, z jakim kiedykolwiek się zetknąłem - anonymous