Vairāk
Kā atgriezt asinhronā izsaukuma atbildi?
Man ir funkcija foo
, kas veic Ajax pieprasījumu. Kā es varu atgriezt atbildi no foo
?
Es mēģināju atgriezt vērtību no success
callback, kā arī piešķirt atbildi vietējam mainīgajam funkcijas iekšienē un atgriezt to, bet neviens no šiem veidiem faktiski neatgriež atbildi.
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
Lai gan
findItem
izpilde var aizņemt ilgu laiku, jebkuram kodam, kas nāk pēcvar item = findItem();
, ir jāgaida, līdz funkcija atgriež rezultātu.Asinhronā
Jūs atkal izsaucat savu draugu tā paša iemesla dēļ. Bet šoreiz jūs viņam sakāt, ka steidzaties un viņam vajadzētu atzvanīt jums atpakaļ pa mobilo tālruni. Jūs nokārtojat klausuli, izejat no mājas un darāt to, ko bijāt ieplānojis darīt. Kad jūsu draugs jums piezvanīs atpakaļ, jūs nodarbojaties ar informāciju, ko viņš jums nodevis. Tieši tas notiek, kad jūs veicat Ajax pieprasījumu.
Tā vietā, lai gaidītu atbildi, izpilde tiek turpināta nekavējoties, un tiek izpildīts paziņojums pēc Ajax izsaukuma. Lai galu galā saņemtu atbildi, jūs sniedzat funkciju, kas jāizsauc pēc atbildes saņemšanas, callback (pamanāt kaut ko? call back ?). Jebkurš paziņojums, kas nāk pēc šī izsaukuma, tiek izpildīts, pirms tiek izsaukts atpakaļsaukums.
Risinājums(-i)
Pieļaujiet JavaScript asinhrono dabu! Lai gan dažas asinhronās operācijas nodrošina sinhronus analogus (tāpat arī "Ajax"), parasti nav ieteicams tās izmantot, īpaši pārlūkprogrammā. Kāpēc tas ir slikti, vai jūs jautājat? JavaScript darbojas pārlūkprogrammas lietotāja saskarnes pavedienā, un jebkurš ilgstoši notiekošs process bloķēs lietotāja saskarni, padarot to neatbilstošu. Turklāt JavaScript izpildes laikam ir noteikts maksimālais izpildes laiks, un pārlūkprogramma jautās lietotājam, vai turpināt izpildi vai nē. Tas viss ir ļoti slikta lietotāja pieredze. Lietotājs nevarēs noteikt, vai viss darbojas pareizi vai nē. Turklāt šis efekts būs vēl sliktāks lietotājiem ar lēnu savienojumu. Turpmāk mēs aplūkosim trīs dažādus risinājumus, kas visi ir uzbūvēti viens uz otra:
Solījumi ar
then()
(ES2015+, pieejami vecākās pārlūkprogrammās, ja izmantojat kādu no daudzajām solījumu bibliotēkām). Visas trīs ir pieejamas pašreizējās pārlūkprogrammās un mezglā 7+.ES2017+: Solījumi ar
async/await
async
unawait
palīdzību jūs varat rakstīt asinhronās funkcijas "sinhronā stilā". Kods joprojām ir asinhronais, bet tas ir vieglāk lasāms/izprotams.async/await
balstās uz solījumiem:async
funkcija vienmēr atgriež solījumu.await
"izvērš solījumu" un vai nu iegūst vērtību, ar kādu solījums tika atrisināts, vai arī izmet kļūdu, ja solījums tika noraidīts. Svarīgi: Jūs varat izmantotawait
tikaiasync
funkcijas iekšienē. Pašlaik augstākā līmeņaawait
vēl nav atbalstīta, tāpēc, lai sāktuasync
kontekstu, jums, iespējams, būs jāizveido async IIFE (Tūlīt izsauktas funkcijas izteiksme). Vairāk parasync
unawait
var izlasīt MDN. Šeit ir piemērs, kas balstās uz iepriekš minēto kavēšanos:Pašreizējās pārlūkprogrammas un mezgla versijas atbalsta
async/await
. Varat atbalstīt arī vecākas vides, pārveidojot savu kodu uz ES5 ar reģeneratora palīdzību (vai rīkiem, kas izmanto reģeneratoru, piemēram, Babel).Ļaujiet funkcijām pieņemt callbacks
Atgriezeniskais zvans ir vienkārši funkcija, kas nodota citai funkcijai. Šī cita funkcija var izsaukt nodoto funkciju, kad vien tā ir gatava. Asinhronā procesa kontekstā atgriezeniskais izsaukums tiks izsaukts, kad vien asinhronais process būs pabeigts. Parasti atpakaļsaukumam parasti tiek nodots rezultāts. Jautājuma piemērā jūs varat likt
foo
pieņemt atpakaļsaukumu un izmantot to kāsuccess
atpakaļsaukumu. Tātad šiskļūst par
Šeit mēs definējām funkciju "inline", bet jūs varat nodot jebkuras funkcijas atsauci:
Pati
foo
ir definēta šādi:callback
atsaucas uz funkciju, ko mēs nododamfoo
, kad to izsaucam, un mēs to vienkārši nododam tālāksuccess
. T.i., kad Ajax pieprasījums būs veiksmīgs,$.ajax
izsaukscallback
un nodos atbildi atpakaļsaukumam (uz kuru var atsaukties arresult
, jo tieši tā mēs definējām atpakaļsaukumu). Jūs varat arī apstrādāt atbildi pirms tās nodošanas atpakaļsaukumam:Kodu rakstīt, izmantojot atgriezeniskos izsaukumus, ir vienkāršāk, nekā varētu šķist. Galu galā JavaScript pārlūkprogrammā ir lielā mērā orientēts uz notikumiem (DOM notikumiem). Ajax atbildes saņemšana nav nekas cits kā notikums.
Grūtības var rasties, ja jāstrādā ar trešo pušu kodu, taču lielāko daļu problēmu var atrisināt, vienkārši pārdomājot lietojumprogrammas plūsmu.
ES2015+: then(): solījumi ar then()
Promise API ir jauna ECMAScript 6 (ES2015) funkcija, taču tai jau ir labs pārlūkprogrammas atbalsts. Ir arī daudzas bibliotēkas, kas implementē standarta Promises API un nodrošina papildu metodes, lai atvieglotu asinhrono funkciju izmantošanu un veidošanu (piemēram, bluebird). Promises ir konteineri nākotnes vērtībām. Kad solījums saņem vērtību (tā ir atrisināta) vai kad tas tiek atcelts (atteikts), tas informē visus savus "klausītājus", kuri vēlas piekļūt šai vērtībai. Priekšrocība salīdzinājumā ar parastajiem atpakaļsaukumiem ir tā, ka tie ļauj atdalīt kodu un tos ir vieglāk veidot. Šeit ir vienkāršs solījuma izmantošanas piemērs:
Piemērojot mūsu Ajax izsaukumam, mēs varētu izmantot šādus solījumus:
Visu solījumu piedāvāto priekšrocību aprakstīšana pārsniedz šīs atbildes apjomu, taču, ja rakstāt jaunu kodu, jums vajadzētu nopietni apsvērt to izmantošanu. Tie nodrošina lielisku abstrakciju un jūsu koda atdalīšanu. Vairāk informācijas par solījumiem: HTML5 rocks - JavaScript Promises.
Piebilde: jQuery's atliktie objekti
Deferred objects ir jQuery's pielāgota solījumu implementācija (pirms solījumu API tika standartizēts). Tie uzvedas gandrīz tāpat kā solījumi, bet piedāvā nedaudz atšķirīgu API. Katra jQuery Ajax metode jau atgriež "atlikto objektu" (patiesībā atliktā objekta solījumu), ko jūs varat vienkārši atgriezt no savas funkcijas:
Piebilde: Promise gotchas
Paturiet prātā, ka solījumi un atliktie objekti ir tikai konteineri nākotnes vērtībai, tie nav pati vērtība. Piemēram, pieņemsim, ka jums ir šādi:
Šis kods nepareizi saprot iepriekš minētās asinhronitātes problēmas. Konkrēti,
$.ajax()
neaptur kodu, kamēr tas pārbauda '/password' lapu jūsu serverī - tas nosūta pieprasījumu uz serveri un, kamēr tas gaida, nekavējoties atgriež jQuery Ajax Deferred objektu, nevis atbildi no servera. Tas nozīmē, kaif
paziņojums vienmēr saņems šo Deferred objektu, uzskatīs to partrue
un turpinās darbu tā, it kā lietotājs būtu pieteicies. Tas nav labi. Bet labojums ir vienkāršs:Nav ieteicams: Sinhronie "Ajax" izsaukumi
Kā jau minēju, dažām(!) asinhronajām operācijām ir sinhroni ekvivalenti. Es neatbalstu to lietošanu, bet pilnīguma labad šeit ir norādīts, kā jūs varētu veikt sinhrono izsaukumu:
Bez jQuery
Ja jūs tieši izmantojat
XMLHTTPRequest
objektu, kā trešo argumentu.open
nododietfalse
.jQuery
Ja izmantojat jQuery, varat iestatīt
async
opciju uzfalse
. Ņemiet vērā, ka šī opcija ir atcelta kopš jQuery 1.8. Tad jūs varat izmantotsuccess
izsaukumu vai piekļūt jqXHR objektaresponseText
īpašībai:Ja izmantojat jebkuru citu jQuery Ajax metodi, piemēram,
$.get
,$.getJSON
u.c., jums tā jāmaina uz$.ajax
(jo$.ajax
var nodot tikai konfigurācijas parametrus). Atgādinām! Nav iespējams veikt sinhrono JSONP pieprasījumu. JSONP pēc savas būtības vienmēr ir asinhronais (vēl viens iemesls, kāpēc pat neapsvērt šo iespēju).Ja jūs neizmantojat jQuery savā kodā, šī atbilde ir domāta jums.
Jūsu kodam vajadzētu būt apmēram šādam:
Felix Kling paveica lielisku darbu, rakstot atbildi cilvēkiem, kas izmanto jQuery AJAX, es nolēmu piedāvāt alternatīvu tiem, kas to nedara.
(Piezīme, tiem, kas izmanto jauno
fetch
API, Angular vai promises, esmu pievienojis citu atbildi zemāk).Kas jums'ir jārisina
Šis ir īss kopsavilkums "Problēmas skaidrojums" no citas atbildes, ja pēc šīs izlasīšanas neesat pārliecināts, izlasiet to.
AJAX A apzīmē asinhrono. Tas nozīmē, ka pieprasījuma nosūtīšana (vai drīzāk atbildes saņemšana) tiek izņemta no parastās izpildes plūsmas. Jūsu piemērā
.send
atgriežas uzreiz, un nākamais paziņojumsreturn result;
tiek izpildīts, pirms vēl tika izsaukta funkcija, kuru jūs nodevāt kāsuccess
izsaukumu.Tas nozīmē, ka atgriešanās brīdī jūsu definētais klausītājs vēl nav izpildīts, un tas nozīmē, ka vērtība, kuru jūs atgriežat, vēl nav definēta.
Šeit ir vienkārša analoģija
[(Fiddle)][2]
Atgrieztā
a
vērtība irnedefinēta
, joa=5
daļa vēl nav izpildīta. AJAX darbojas šādi - jūs atdodat vērtību, pirms serveris ir paspējis paziņot pārlūkprogrammai, kāda ir šī vērtība.Viens no iespējamiem šīs problēmas risinājumiem ir kodēt reaktīvi , norādot programmai, ko darīt, kad aprēķins ir pabeigts.
To sauc par CPS. Būtībā mēs nododam
getFive
darbību, kas jāveic, kad tā tiek pabeigta, un mēs sakām savam kodam, kā reaģēt, kad notikums ir pabeigts (piemēram, mūsu AJAX izsaukums vai, šajā gadījumā, laika limits).Lietošana būtu šāda:
Kādam ekrānā jāparādās brīdinājumam "5". [(Fiddle)][4].
Iespējamie risinājumi
Pamatā ir divi veidi, kā to atrisināt:
1. Sinhronais AJAX - nedariet tā!!
Kas attiecas uz sinhrono AJAX, nevajag to darīt! Felix's atbildē ir minēti daži pārliecinoši argumenti, kāpēc tā ir slikta ideja. Rezumējot, tas iesaldēs lietotāja pārlūkprogrammu, līdz serveris atgriezīs atbildi, un radīs ļoti sliktu pieredzi lietotājam. Šeit ir vēl viens īss kopsavilkums no MDN par to, kāpēc:
XMLHttpRequest atbalsta gan sinhrono, gan asinhrono komunikāciju. Tomēr kopumā veiktspējas apsvērumu dēļ priekšroka jādod asinhroniem pieprasījumiem, nevis sinhroniem pieprasījumiem.
Ja jums tas ir jādara, varat izmantot karodziņu: Lūk, kā:
2. Pārstrukturēt kodu
Ļaujiet savai funkcijai pieņemt atpakaļsaukumu. Piemērā redzamajā kodā
foo
var izveidot funkciju, kas pieņem atpakaļsaukumu. Mēs savam kodam norādīsim, kā reaģēt, kadfoo
būs pabeigts.Tātad:
Kļūst:
Šeit mēs nodevuši anonīmu funkciju, bet tikpat labi mēs varētu nodot arī atsauci uz jau esošu funkciju, tādējādi tas izskatās šādi:
Sīkāku informāciju par to, kā tiek veidots šāds atgriezenisko zvanu dizains, atradīsiet Felix'ā atbildē.
Tagad definēsim foo, lai pats attiecīgi rīkotos
[(fiddle)][6]
Tagad mēs esam padarījuši mūsu foo funkciju par darbību, kas tiek izpildīta, kad AJAX ir veiksmīgi pabeigts, mēs varam to paplašināt, pārbaudot, vai atbildes statuss nav 200, un attiecīgi rīkoties (izveidot fail handler u.c.). Tas efektīvi atrisina mūsu problēmu.
Ja jums joprojām ir grūti to saprast izlasiet AJAX sākumposma rokasgrāmatu MDN.
XMLHttpRequest 2 (vispirms izlasiet Benjamin Gruenbaum & amp; Felix Kling atbildes). Ja neizmantojat jQuery un vēlaties jauku, īsu XMLHttpRequest 2, kas darbojas modernās pārlūkprogrammās un arī mobilajās pārlūkprogrammās, iesaku to izmantot šādā veidā:
Kā redzat:
Vai arī, ja kāda iemesla dēļ jūs
pievienojat()
izsaukumu kādai klasei:Piemērs:
Vai (iepriekš minētais ir labāks, anonīmās funkcijas vienmēr ir problēma):
Nekas vienkāršāks. Tagad daži cilvēki droši vien teiks, ka labāk izmantot onreadystatechange vai pat XMLHttpRequest mainīgo nosaukumu. Tas ir nepareizi. Skatiet XMLHttpRequest uzlabotās funkcijas. Tas atbalsta visas *modernās pārlūkprogrammas. Un es to varu apstiprināt, jo izmantoju šo pieeju kopš XMLHttpRequest 2. Man nekad nav bijušas nekāda veida problēmas visās pārlūkprogrammās, ko es izmantoju. onreadystatechange ir noderīgs tikai tad, ja vēlaties saņemt galvenes 2. stāvoklī. Mainīgā
XMLHttpRequest
nosaukuma izmantošana ir vēl viena liela kļūda, jo jums ir jāizpilda atpakaļsaukums onload/oreadystatechange slēgumos, citādi jūs to zaudējat.Ja vēlaties kaut ko sarežģītāku, izmantojot post un FormData, varat viegli paplašināt šo funkciju:
Atkal ... tā ir ļoti īsa funkcija, bet tā iegūst & amp; post. Lietošanas piemēri:
Vai nodot pilnu veidlapas elementu (
document.getElementsByTagName('form')[0]
):Vai arī iestatiet dažas pielāgotas vērtības:
Kā redzat, es neīstenoju sinhronizāciju... tas ir slikti. Ņemot to vērā... kāpēc to nedarīt vienkāršāk?
Kā minēts komentārā, kļūdas && sinhronā izmantošana pilnībā izjauc atbildes jēgu. Kurš ir jauks īss veids, kā izmantot Ajax pareizā veidā? Kļūdu apstrādātājs
Iepriekš minētajā skripta gadījumā jums ir kļūdas apstrādātājs, kas ir statiski definēts, tāpēc tas neapdraud funkciju. Kļūdu apstrādātāju var izmantot arī citām funkcijām. Bet, lai patiešām izsauktu kļūdu, vienīgais veids ir uzrakstīt nepareizu URL, un tādā gadījumā katra pārlūkprogramma izmet kļūdu. Kļūdu apstrādātāji varbūt ir noderīgi, ja iestatāt pielāgotas galvenes, iestatāt responseType uz blob array buferi vai ko citu... Pat tad, ja kā metodi nododat 'POSTAPAPAP', kļūda netiks izmesta. Pat tad, ja kā formdata tiks nodoti 'fdggdgilfdghfldj', kļūda netiks pieļauta. Pirmajā gadījumā kļūda ir
displayAjax()
iekšpusē zemthis.statusText
kāMetode nav atļauta
. Otrajā gadījumā tas vienkārši darbojas. Jums jāpārbauda servera pusē, vai esat nodevis pareizos amata datus. Ja starpdomēna nav atļauta, automātiski tiek izmesta kļūda. Kļūdas atbildē nav kļūdu kodu. Ir tikaithis.type
, kas ir iestatīts uz kļūdu. Kāpēc pievienot kļūdu apstrādātāju, ja jums nav pilnīgas kontroles pār kļūdām? Lielākā daļa kļūdu tiek atgrieztas iekšpusē šajā atpakaļsaukuma funkcijādisplayAjax()
. Tātad: Ja spējat pareizi nokopēt un ielīmēt URL adresi, tad kļūdu pārbaude nav nepieciešama. ;) PS: Kā pirmo testu es uzrakstīju x('x', displayAjax)..., un tas pilnīgi saņēma atbildi...??????? Tad es pārbaudīju mapi, kurā atrodas HTML, un tur bija fails ar nosaukumu 'x.xml'. Tātad, pat ja jūs aizmirsāt faila paplašinājumu, XMLHttpRequest 2 to atradīs. Es LOL'dSinhronizēta faila lasīšana Tā nedariet. Ja vēlaties uz brīdi bloķēt pārlūkprogrammu, ielādējiet labu lielu
.txt
failu sinhronizēti.Tagad jūs varat veikt
Nav cita veida, kā to izdarīt neasinhroni. (Jā, ar setTimeout cilpu... bet nopietni?) Ja jūs strādājat ar API vai tikai ar saviem saraksta failiem, vai ar ko citu, jūs vienmēr izmantojat dažādas funkcijas katram pieprasījumam... Tikai tad, ja jums ir lapa, kurā vienmēr ielādējat vienu un to pašu XML/JSON vai ko citu, jums ir vajadzīga tikai viena funkcija. Tādā gadījumā nedaudz modificējiet Ajax funkciju un aizstāt b ar savu īpašo funkciju.
Iepriekš minētās funkcijas ir paredzētas pamata lietošanai. Ja vēlaties paplašināt funkciju... Jā, varat. Es'izmantoju daudz API, un viena no pirmajām funkcijām, ko integrēju katrā HTML lapā, ir pirmā Ajax funkcija šajā atbildē, tikai ar GET... Bet ar XMLHttpRequest 2 var izdarīt daudz ko: Es veicu lejupielādes pārvaldnieks (izmantojot diapazonus abās pusēs ar atsākšanu, filereader, failu sistēma), dažādi attēlu izmēru pārveidotāji, izmantojot audekls, aizpildīt web SQL datu bāzes ar base64images un daudz ko citu... Bet šajos gadījumos jums vajadzētu izveidot funkciju tikai šim nolūkam... dažreiz jums ir nepieciešams blob, masīva buferi, jūs varat iestatīt galvenes, override mimetype un ir daudz vairāk... Bet jautājums ir par to, kā atgriezt Ajax atbildi... (Es pievienoju vienkāršu veidu.)