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`.
→ Vispārīgāku skaidrojumu par asinhronizētu darbību ar dažādiem piemēriem skatīt https://stackoverflow.com/q/23667086/218196
→ Ja problēmu jau saprotat, pārejiet pie tālāk sniegtajiem iespējamiem risinājumiem.
Problēma
Burts A Ajax apzīmē asinhrono . Tas nozīmē, ka pieprasījuma nosūtīšana (vai drīzāk atbildes saņemšana) ir izņemta no parastās izpildes plūsmas. Jūsu piemērā
$.ajax
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
atgriezenisko zvanu. Lūk, analoģija, kas, cerams, skaidrāk izskaidros atšķirību starp sinhrono un asinhrono plūsmu:Sinhronā
Iedomājieties, ka jūs piezvanāt draugam un lūdzat viņu kaut ko sameklēt. Lai gan tas var aizņemt kādu laiku, jūs gaidāt pie tālruņa un skatāties telpā, līdz jūsu draugs sniedz jums vajadzīgo atbildi. Tas pats notiek, kad veicat funkcijas izsaukumu, kas satur "normālu" kodu:
function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}
var item = findItem();
// Do something with item
doSomethingElse();
Lai gan findItem
izpilde var aizņemt ilgu laiku, jebkuram kodam, kas nāk pēc var item = findItem();
, ir jāgaida, līdz funkcija atgriež rezultātu.
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.
findItem(function(item) {
// Do something with item
});
doSomethingElse();
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:
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+.async/await
async
un await
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 izmantot await
tikai async
funkcijas iekšienē. Pašlaik augstākā līmeņa await
vēl nav atbalstīta, tāpēc, lai sāktu async
kontekstu, jums, iespējams, būs jāizveido async IIFE (Tūlīt izsauktas funkcijas izteiksme).
Vairāk par async
un await
var izlasīt MDN.
Šeit ir piemērs, kas balstās uz iepriekš minēto kavēšanos:// 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 3 seconds (just for the sake of this example)
await delay();
// 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;
}
}
// Start an IIFE to use `await` at the top level
(async function(){
let books = await getAllBooks();
console.log(books);
})();
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).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 šis
var result = foo();
// Code that depends on 'result'
kļūst par
foo(function(result) {
// Code that depends on 'result'
});
Šeit mēs definējām funkciju "inline", bet jūs varat nodot jebkuras funkcijas atsauci:
function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);
Pati foo
ir definēta šādi:
function foo(callback) {
$.ajax({
// ...
success: callback
});
}
callback
atsaucas uz funkciju, ko mēs nododam foo
, kad to izsaucam, un mēs to vienkārši nododam tālāk success
. T.i., kad Ajax pieprasījums būs veiksmīgs, $.ajax
izsauks callback
un nodos atbildi atpakaļsaukumam (uz kuru var atsaukties ar result
, 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:
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
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:
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).
});
Piemērojot mūsu Ajax izsaukumam, mēs varētu izmantot šādus solījumus:
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
});
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.
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:
function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
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:
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
}
Š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ē, ka if
paziņojums vienmēr saņems šo Deferred objektu, uzskatīs to par true
un turpinās darbu tā, it kā lietotājs būtu pieteicies. Tas nav labi.
Bet labojums ir vienkāršs:
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
});
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:
Ja jūs tieši izmantojat XMLHTTPRequest
objektu, kā trešo argumentu .open
nododiet false
.
Ja izmantojat jQuery, varat iestatīt async
opciju uz false
. Ņemiet vērā, ka šī opcija ir atcelta kopš jQuery 1.8.
Tad jūs varat izmantot success
izsaukumu vai piekļūt jqXHR objekta responseText
īpašībai:
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
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).
Jūsu kodam vajadzētu būt apmēram šādam:
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 paveica lielisku darbu, rakstot atbildi cilvēkiem, kas izmanto jQuery AJAX, es nolēmu piedāvāt alternatīvu tiem, kas to nedara.
Š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ņojums return 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
function getFive(){
var a;
setTimeout(function(){
a=5;
},10);
return a;
}
[(Fiddle)][2]
Atgrieztā a
vērtība ir nedefinēta
, jo a=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.
function onComplete(a){ // When the code completes, do this
alert(a);
}
function getFive(whenDone){
var a;
setTimeout(function(){
a=5;
whenDone(a);
},10);
}
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:
getFive(onComplete);
Kādam ekrānā jāparādās brīdinājumam "5". [(Fiddle)][4].
Pamatā ir divi veidi, kā to atrisinā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.
Īsāk sakot, sinhronie pieprasījumi bloķē koda izpildi... ...tas var radīt nopietnas problēmas...
Ja jums tas ir jādara, varat izmantot karodziņu: Lūk, kā:
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);
}
Ļ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, kad foo
būs pabeigts.
Tātad:
var result = foo();
// code that depends on `result` goes here
Kļūst:
foo(function(result) {
// code that depends on `result`
});
Š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:
function myHandler(result) {
// code that depends on `result`
}
foo(myHandler);
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
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();
}
[(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ā:
function ajax(a, b, c){ // URL, callback, just a placeholder
c = new XMLHttpRequest;
c.open('GET', a);
c.onload = b;
c.send()
}
Kā redzat:
this.response
Vai arī, ja kāda iemesla dēļ jūs pievienojat()
izsaukumu kādai klasei:
e.target.response
Piemērs:
function callback(e){
console.log(this.response);
}
ajax('URL', callback);
Vai (iepriekš minētais ir labāks, anonīmās funkcijas vienmēr ir problēma):
ajax('URL', function(e){console.log(this.response)});
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:
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)
}
Atkal ... tā ir ļoti īsa funkcija, bet tā iegūst & amp; post. Lietošanas piemēri:
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
Vai nodot pilnu veidlapas elementu (document.getElementsByTagName('form')[0]
):
var fd = new FormData(form);
x(url, callback, 'post', fd);
Vai arī iestatiet dažas pielāgotas vērtības:
var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);
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
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);
displayAjax()
iekšpusē zem this.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 tikai this.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.
function omg(a, c){ // URL
c = new XMLHttpRequest;
c.open('GET', a, true);
c.send();
return c; // Or c.response
}
Tagad jūs varat veikt
var res = omg('thisIsGonnaBlockThePage.txt');
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.)