Viac na
Ako vrátim odpoveď z asynchrónneho volania?
Mám funkciu foo
, ktorá vykoná požiadavku Ajax. Ako môžem vrátiť odpoveď z funkcie foo
?
Skúsil som vrátiť hodnotu zo spätného volania success
, ako aj priradiť odpoveď do lokálnej premennej vo vnútri funkcie a vrátiť ju, ale ani jeden z týchto spôsobov nevrátil odpoveď.
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`.
5183
3
Aj keď vykonanie funkcie
findItem
môže trvať dlho, každý kód, ktorý príde zavar item = findItem();
, musí čakať, kým funkcia vráti výsledok.Asynchrónne
Z toho istého dôvodu zavoláte svojho priateľa znova. Tentoraz mu však poviete, že sa ponáhľate a mal by vám zavolať späť na mobilný telefón. Zavesíte, odídete z domu a urobíte to, čo ste mali v pláne. Keď vám váš priateľ zavolá späť, zaoberáte sa informáciami, ktoré vám poskytol. Presne to'sa deje, keď vykonáte požiadavku Ajax.
Namiesto čakania na odpoveď sa okamžite pokračuje vo vykonávaní a vykoná sa príkaz po volaní Ajax. Aby ste nakoniec dostali odpoveď, poskytnete funkciu, ktorá sa zavolá po prijatí odpovede, callback (všimli ste si niečo? call back ?). Akýkoľvek príkaz prichádzajúci po tomto volaní sa vykoná skôr, ako sa zavolá spätné volanie.
Riešenie(-ia)
Prijmite asynchrónnu povahu JavaScriptu! Hoci niektoré asynchrónne operácie poskytujú synchrónne náprotivky (tak je to aj v prípade "Ajax"), vo všeobecnosti sa neodporúča ich používať, najmä v kontexte prehliadača. Pýtate sa, prečo je to zlé? JavaScript beží vo vlákne používateľského rozhrania prehliadača a akýkoľvek dlho bežiaci proces zablokuje používateľské rozhranie, takže nebude reagovať. Okrem toho existuje horný limit času vykonávania JavaScriptu a prehliadač sa používateľa opýta, či má pokračovať vo vykonávaní alebo nie. To všetko je pre používateľa naozaj nepríjemné. Používateľ nebude schopný zistiť, či všetko funguje v poriadku alebo nie. Okrem toho bude tento efekt horší pre používateľov s pomalým pripojením. V nasledujúcom texte sa pozrieme na tri rôzne riešenia, ktoré na seba nadväzujú:
async/await
(ES2017+, dostupné v starších prehliadačoch, ak používate transpiler alebo regenerátor)Sľuby s
then()
(ES2015+, dostupné v starších prehliadačoch, ak používate jednu z mnohých knižníc sľubov) Všetky tri sú k dispozícii v súčasných prehliadačoch a v uzle 7+.ES2017+: Prísľuby s
async/await
Verzia jazyka ECMAScript vydaná v roku 2017 zaviedla podporu asynchrónnych funkcií na úrovni syntaxe. Pomocou
async
aawait
môžete písať asynchrónne v "synchrónnom štýle". Kód je stále asynchrónny, ale je'ľahšie čitateľný/pochopiteľný.async/await
stavia na prísľuboch: funkciaasync
vždy vracia prísľub.await
"rozbalí" prísľub a buď výsledkom je hodnota, s ktorou bol prísľub vyriešený, alebo vyhodí chybu, ak bol prísľub odmietnutý. Dôležité: Funkciuawait
môžete použiť len vo vnútri funkcieasync
. Momentálne ešte nie je podporovaná najvyššia úroveňawait
, takže možno budete musieť vytvoriť asynchrónny IIFE (Immediately Invoked Function Expression) na spustenie kontextuasync
. Viac informácií oasync
aawait
nájdete na MDN. Tu je príklad, ktorý nadväzuje na vyššie uvedené oneskorenie:Súčasné verzie prehliadača a uzla podporujú
async/await
. Staršie prostredia môžete podporovať aj transformáciou kódu na ES5 pomocou regenerator (alebo nástrojov, ktoré používajú regenerator, ako napríklad Babel).Nechajte funkcie prijímať callbacky
Spätné volanie je jednoducho funkcia odovzdaná inej funkcii. Táto iná funkcia môže zavolať odovzdanú funkciu vždy, keď je pripravená. V kontexte asynchrónneho procesu sa spätné volanie zavolá vždy, keď je asynchrónny proces hotový. Zvyčajne sa spätnému volaniu odovzdáva výsledok. V príklade z otázky môžete urobiť tak, že
foo
prijme spätné volanie a použije ho ako spätné volaniesuccess
. Takže totosa stane
Tu sme definovali funkciu "inline", ale môžete odovzdať akýkoľvek odkaz na funkciu:
Samotné
foo
je definované takto:callback
bude odkazovať na funkciu, ktorú odovzdámefoo
pri jej volaní a jednoducho ju odovzdámesuccess
. T.j. keď je požiadavka Ajaxu úspešná,$.ajax
zavolácallback
a odovzdá odpoveď spätnému volaniu (na ktoré sa môžeme odvolať pomocouresult
, keďže takto sme definovali spätné volanie). Odpoveď môžete spracovať aj pred jej odovzdaním spätnému volaniu:Písanie kódu pomocou spätných volaní je jednoduchšie, ako sa môže zdať. Koniec koncov, JavaScript v prehliadači je vo veľkej miere riadený udalosťami (udalosťami DOM). Prijatie odpovede Ajaxu nie je nič iné ako udalosť.
Ťažkosti by mohli nastať, keď budete musieť pracovať s kódom tretích strán, ale väčšinu problémov možno vyriešiť jednoduchým premyslením toku aplikácie.
ES2015+: then()
Rozhranie Promise API je novou funkciou jazyka ECMAScript 6 (ES2015), ale má už dobrú podporu prehliadačov. Existuje aj mnoho knižníc, ktoré implementujú štandardné API Promises a poskytujú ďalšie metódy na uľahčenie používania a skladania asynchrónnych funkcií (napr. bluebird). Prísľuby sú kontajnery pre budúce hodnoty. Keď sľub prijme hodnotu (je vyriešený) alebo keď je zrušený (odmietnutý), oznámi to všetkým svojim "poslucháčom", ktorí chcú k tejto hodnote pristupovať. Výhodou oproti obyčajným spätným volaniam je, že umožňujú oddeliť kód a ľahšie sa skladajú. Tu je jednoduchý príklad použitia sľubu:
Pri použití na naše volanie Ajaxu by sme mohli použiť sľuby takto:
Opis všetkých výhod, ktoré sľuby ponúkajú, je nad rámec tejto odpovede, ale ak píšete nový kód, mali by ste o nich vážne uvažovať. Poskytujú skvelú abstrakciu a oddelenie vášho kódu. Ďalšie informácie o sľuboch: HTML5 rocks - JavaScript Promises.
Vedľajšia poznámka: jQuery's deferred objects
Deferred objects sú vlastnou implementáciou sľubov v jQuery (pred štandardizáciou API Promise). Správajú sa takmer ako sľuby, ale poskytujú trochu iné API. Každá Ajaxová metóda jQuery už vracia "deferred object" (v skutočnosti prísľub deferred objektu), ktorý môžete jednoducho vrátiť z vašej funkcie:
Vedľajšia poznámka: Promise gotchas
Nezabudnite, že sľuby a odložené objekty sú len kontajnery pre budúcu hodnotu, nie sú samotnou hodnotou. Predpokladajme napríklad, že máte nasledovné:
Tento kód nesprávne chápe vyššie uvedené problémy asynchrónie. Konkrétne,
$.ajax()
nezastaví kód, kým kontroluje stránku '/heslo' na vašom serveri - odošle požiadavku na server a kým čaká, okamžite vráti objekt jQuery Ajax Deferred, nie odpoveď zo servera. To znamená, že príkazif
vždy získa tento objekt Deferred, bude ho považovať zatrue
a bude pokračovať, ako keby bol používateľ prihlásený. To nie je dobré. Oprava je však jednoduchá:Neodporúča sa: Synchrónne "Ajax" volania
Ako som už spomenul, niektoré(!) asynchrónne operácie majú synchrónne ekvivalenty. Neobhajujem ich používanie, ale pre úplnosť uvádzam, ako by ste vykonali synchrónne volanie:
Bez jQuery
Ak priamo použijete objekt
XMLHTTPRequest
, odovzdajte ako tretí argument príkazu.open
príkazfalse
.jQuery
Ak používate jQuery, môžete nastaviť možnosť
async
na hodnotufalse
. Všimnite si, že táto možnosť je zrušená od verzie jQuery 1.8. Potom môžete stále používať spätné volaniesuccess
alebo pristupovať k vlastnostiresponseText
objektu jqXHR:Ak používate akúkoľvek inú metódu jQuery Ajax, napríklad
$.get
,$.getJSON
atď., musíte ju zmeniť na$.ajax
(pretože$.ajax
môžete odovzdať iba konfiguračné parametre). Upozornenie! Nie je možné vykonať synchrónnu požiadavku JSONP. JSONP je zo svojej podstaty vždy asynchrónny (ďalší dôvod, prečo o tejto možnosti ani neuvažovať).Ak vo svojom kóde nepoužívate jQuery, táto odpoveď je pre vás
Váš kód by mal vyzerať približne takto:
Felix Kling odviedol dobrú prácu pri písaní odpovede pre ľudí, ktorí používajú jQuery pre AJAX, ja'som sa rozhodol poskytnúť alternatívu pre ľudí, ktorí ju nepoužívajú.
(Poznámka, pre tých, ktorí používajú nové rozhranie API
fetch
, Angular alebo promises, som nižšie pridal ďalšiu odpoveď)Čomu čelíte
Toto je krátke zhrnutie "Vysvetlenia problému" z inej odpovede, ak si po prečítaní tohto nie ste istí, prečítajte si to.
Skratka A v slove AJAX znamená asynchrónny. To znamená, že odoslanie požiadavky (alebo skôr prijatie odpovede) je vyňaté z normálneho toku vykonávania. Vo vašom príklade sa príkaz
.send
vráti okamžite a ďalší príkaz,return result;
, sa vykoná ešte pred tým, ako bola zavolaná funkcia, ktorú ste odovzdali ako spätné volaniesuccess
.To znamená, že keď'sa vracia, poslucháč, ktorý ste'definovali, sa ešte nevykonal, čo znamená, že hodnota, ktorú vraciate, ešte nebola definovaná.
Tu je jednoduchá analógia
[(Fiddle)][2]
Vrátená hodnota
a
jenedefinovaná
, pretože časťa=5
sa ešte nevykonala. AJAX sa správa tak, že vraciate hodnotu skôr, ako server dostal šancu povedať prehliadaču, aká je táto hodnota.Jedným z možných riešení tohto problému je kódovať reaktívne , čím svojmu programu poviete, čo má urobiť po dokončení výpočtu.
Tento postup sa nazýva CPS. V podstate'odovzdávame
getFive
akciu, ktorú má vykonať po dokončení, hovoríme nášmu kódu, ako má reagovať po dokončení udalosti (ako je naše volanie AJAXu alebo v tomto prípade časový limit).Použitie by bolo nasledovné:
Čo by malo upozorniť "5" na obrazovke. [(Fiddle)][4].
Možné riešenia
V zásade existujú dva spôsoby, ako to vyriešiť:
1. Synchrónny AJAX - nerobte to!!
Čo sa týka synchrónneho AJAXu, nepoužívajte ho! Felix vo svojej odpovedi uvádza niekoľko presvedčivých argumentov, prečo je to zlý nápad. Ak to zhrnieme, zamrzne používateľovi prehliadač, kým server nevráti odpoveď, a vytvorí veľmi zlú používateľskú skúsenosť. Tu je ďalšie krátke zhrnutie z MDN o tom, prečo:
XMLHttpRequest podporuje synchrónnu aj asynchrónnu komunikáciu. Vo všeobecnosti by sa však z výkonnostných dôvodov mali uprednostňovať asynchrónne požiadavky pred synchrónnymi.
Ak to musíte urobiť, môžete odovzdať príznak: Tu je návod ako:
2. Reštrukturalizujte kód
Nechajte svoju funkciu prijať spätné volanie. V príklade kódu
foo
môže byť funkcia prijatá ako spätné volanie. Budeme nášmu kódu hovoriť, ako má reagovať, keď safoo
dokončí.Takže:
sa stáva:
Tu sme odovzdali anonymnú funkciu, ale rovnako ľahko by sme mohli odovzdať odkaz na existujúcu funkciu, takže by to vyzeralo takto:
Podrobnejšie informácie o tom, ako sa tento druh spätného volania navrhuje, nájdete v odpovedi Felixa.
Teraz definujme samotné foo, aby sa správalo zodpovedajúcim spôsobom
[(fiddle)][6]
Teraz sme vytvorili našu funkciu foo, ktorá akceptuje akciu, ktorá sa spustí po úspešnom dokončení AJAXu, môžeme ju ďalej rozšíriť o kontrolu, či stav odpovede nie je 200, a podľa toho konať (vytvoriť obslužný program fail a podobne). Efektívne tak vyriešite náš problém.
Ak'to stále nechápete prečítajte si príručku AJAX getting started guide na MDN.
XMLHttpRequest 2 (najprv si prečítajte odpovede od Benjamin Gruenbaum & Felix Kling) Ak nepoužívate jQuery a chcete pekný krátky XMLHttpRequest 2, ktorý funguje v moderných prehliadačoch a tiež v mobilných prehliadačoch, odporúčam ho použiť týmto spôsobom:
Ako vidíte:
Alebo ak z nejakého dôvodu
priviažete()
spätné volanie na triedu:Príklad:
Alebo (vyššie uvedený je lepší anonymné funkcie sú vždy problém):
Nič jednoduchšie. Niektorí ľudia teraz pravdepodobne povedia, že je'lepšie použiť onreadystatechange alebo dokonca názov premennej XMLHttpRequest. To'je nesprávne. Pozrite si XMLHttpRequest advanced features. Podporuje všetky *moderné prehliadače. A môžem to potvrdiť, pretože tento prístup používam, odkedy existuje XMLHttpRequest 2. Nikdy som nemal žiadny typ problému na všetkých prehliadačoch, ktoré používam. Onreadystatechange je užitočný len vtedy, ak chcete získať hlavičky v stave 2. Použitie názvu premennej
XMLHttpRequest
je ďalšia veľká chyba, pretože musíte vykonať spätné volanie vnútri uzáverov onload/oreadystatechange, inak ste oň prišli.Ak teraz chcete niečo zložitejšie pomocou post a FormData, môžete túto funkciu jednoducho rozšíriť:
Opäť ... je to'veľmi krátka funkcia, ale dostane & post. Príklady použitia:
Alebo odovzdajte celý prvok formulára (
document.getElementsByTagName('form')[0]
):Alebo nastavte nejaké vlastné hodnoty:
Ako vidíte, neimplementoval som synchronizáciu... je to'zlá vec. Keď už som to povedal... prečo to neurobiť jednoduchým spôsobom?
Ako je uvedené v komentári, použitie chyby && synchronous úplne rozbíja zmysel odpovede. Ktorý je pekný krátky spôsob použitia Ajaxu správnym spôsobom? Obsluha chyby
Vo vyššie uvedenom skripte máte obsluhu chyby, ktorá je staticky definovaná, takže neohrozuje funkciu. Obsluha chyby sa dá použiť aj pre iné funkcie. Ak však chcete skutočne vyvolať chybu, jediným spôsobom je napísať nesprávnu adresu URL, v takom prípade každý prehliadač vyhodí chybu. Obsluhy chýb sú možno užitočné, ak nastavíte vlastné hlavičky, nastavíte responseType na blob array buffer alebo čokoľvek iné... Aj keď ako metódu zadáte 'POSTAPAPAP', nevyhodí to chybu. Aj keď ako formdata odovzdáte 'fdggdgilfdghfldj', nevyhodí to chybu. V prvom prípade je chyba vo vnútri
displayAjax()
podthis.statusText
akoMethod not Allowed
. V druhom prípade to jednoducho funguje. Musíte skontrolovať na strane servera, či ste odovzdali správne údaje o príspevku. Cross-domain not allowed automaticky vyhodí chybu. V chybovej odpovedi nie sú uvedené žiadne chybové kódy. Je tam lenthis.type
, ktorý je nastavený na error. Načo pridávať obsluhu chýb, keď nad chybami nemáte absolútne žiadnu kontrolu? Väčšina chýb sa vracia vo vnútri tohto vo funkcii spätného volaniadisplayAjax()
. Takže: Ak ste schopní správne skopírovať a vložiť URL, nie je potrebné kontrolovať chyby.) PS: Ako prvý test som napísal x('x', displayAjax)..., a úplne to dostalo odpoveď...??? Tak som skontroloval priečinok, kde sa nachádza HTML, a tam bol súbor s názvom 'x.xml'. Takže aj keď zabudnete na príponu vášho súboru XMLHttpRequest 2 ho nájde. LOL'd somSynchrónne čítanie súboru Nedávajte to dokopy. Ak chcete na chvíľu zablokovať prehliadač, načítajte pekný veľký súbor
.txt
synchrónne.Teraz môžete urobiť
Neexistuje žiadny iný spôsob, ako to urobiť neasynchrónnym spôsobom. (Áno, s cyklom setTimeout... ale vážne?) Ďalším bodom je... ak pracujete s API alebo len s vlastnými zoznam'mi súborov alebo čímkoľvek iným, vždy používate pre každú požiadavku iné funkcie... Iba ak máte stránku, kde načítavate vždy to isté XML/JSON alebo čokoľvek iné, potrebujete len jednu funkciu. V takom prípade trochu upravte funkciu Ajax a nahraďte b vašou špeciálnou funkciou.
Uvedené funkcie sú určené na základné použitie. Ak chcete funkciu ROZŠÍRIŤ... Áno, môžete. Ja'používam veľa API a jednou z prvých funkcií, ktoré integrujem do každej stránky HTML, je prvá funkcia Ajax v tejto odpovedi, pričom GET je len... Ale s XMLHttpRequest 2 môžete robiť veľa vecí: Urobil som správcu sťahovania (pomocou rozsahov na oboch stranách s obnovením, filereader, súborový systém), rôzne konvertory veľkosti obrázkov pomocou plátna, naplniť webové databázy SQL s base64images a oveľa viac... Ale v týchto prípadoch by ste mali vytvoriť funkciu len na tento účel... niekedy potrebujete blob, pole bufferov, môžete nastaviť hlavičky, prepísať mimetype a je toho oveľa viac... Ale tu ide o to, ako vrátiť odpoveď Ajaxu... (Pridal som jednoduchý spôsob.)