Kaip grąžinti atsakymą iš asinchroninio skambučio?

Turiu foo funkciją, kuri pateikia Ajax užklausą. Kaip grąžinti atsakymą iš foo ?

Bandžiau grąžinti vertę iš success atšaukimo ir taip pat priskirti atsakymą į vietinį kintamąjį funkcijos viduje ir grąžinti jį, tačiau nė vienas iš šių metodų neatsako.

 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`. 
4687
08 янв. „Felix Kling“ nustatė sausio 08 d 2013-01-08 20:06 '13, 20:06, 2013-01-08 20:06
@ 39 atsakymų
  • 1
  • 2

→ Dėl išsamesnio asinchroninio elgesio paaiškinimo įvairiuose pavyzdžiuose žr. Kodėl mano kintamasis nepasikeičia po to, kai jį pakeičiau funkcijos viduje? - asinchroninis ryšys su kodu

→ Jei jau supratote problemą, eikite į toliau pateiktus galimus sprendimus.

Ši problema

A Ajax reiškia asinchroninį . Tai reiškia, kad užklausos siuntimas (arba, atsakymo gavimas) pašalinamas iš įprastinio vykdymo srauto. Savo pavyzdyje, $.ajax grįžta iš karto, o kitas pranešimas return result; , vykdoma prieš funkciją, kurią išlaikėte kaip success atgalinį pašaukimą.

Čia yra analogija, kuri, tikiuosi, paaiškins skirtumą tarp sinchroninio ir asinchroninio srauto:

sinchroniškai

Įsivaizduokite, kad skambinate draugui ir paprašėte jo surasti kažką jums. Nors tai gali užtrukti šiek tiek laiko, laukite telefono ir žiūrėkite į kosmosą, kol jūsų draugas jums suteiks jums reikalingą atsakymą.

Tas pats atsitinka, kai atliekate funkcijų skambutį, kuriame yra „normalus“ kodas:

 function findItem() { var item; while(item_not_found) { // search } return item; } var item = findItem(); // Do something with item doSomethingElse(); 

Net jei findItem gali užtrukti ilgai, kol bus atliktas findItem , bet koks kodas, kuris seka var item = findItem(); turi laukti, kol funkcija grąžins rezultatą.

Asinchroninis

Dėl tos pačios priežasties dar kartą paskambinate draugui. Bet šį kartą pasakykite jam, kad skubate, ir jis turėtų paskambinti jums atgal į savo mobilųjį telefoną. Jūs pakabinti, palikti namą ir daryti viską, ką planavote. Kai tik jūsų draugas jus paskambins, jums bus suteikta informacija, kurią jis jums davė.

Būtent taip atsitinka, kai pateikiate „Ajax“ užklausą.

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

Užuot laukę atsakymo, vykdymas tęsiamas nedelsiant ir pareiškimas vykdomas po „Ajax“ skambučio. Kad galiausiai gautumėte atsakymą, pateikiate funkciją, kuri bus iškviesta gavus atsakymą, atgalinio ryšio (pažymėkite ką nors, „skambinkite atgal“). Bet koks pareiškimas po šio skambučio vykdomas prieš skambinant atgal.


Sprendimas (-ai)

Priimti asinchroninį „JavaScript“ pobūdį! Nors kai kurios asinchroninės operacijos suteikia sinchroninius kolegas (pvz., „Ajax“), paprastai jie nerekomenduojami naudoti, ypač naršyklės kontekste.

Kodėl tai blogai, paklausti?

„JavaScript“ veikia naršyklės sąsajos sąsajoje, o bet koks ilgas procesas blokuoja vartotojo sąsają, todėl ji nereaguoja. Be to, yra viršutinė „JavaScript“ trukmė, o naršyklė paprašys naudotojo, ar tęsti vykdymą, ar ne.

Visa tai tikrai bloga vartotojo patirtis. Naudotojas negalės pasakyti, ar viskas veikia tinkamai. Be to, poveikis bus blogesnis vartotojams, turintiems lėtą ryšį.

Toliau ieškome trijų skirtingų sprendimų, kurie yra pastatyti vienas ant kito:

  • Pažadai su async/await await (ES2017 +, galima senesnėse naršyklėse, jei naudojate transporterį arba regeneratorių)
  • Atšaukimai (populiarūs svetainėje)
  • then() žada then() (ES2015 +, galima senesnėse naršyklėse, jei naudojate vieną iš daugelio pažadų bibliotekų)

Visi trys yra prieinami dabartinėse naršyklėse ir mazge 7+.


ES2017 +: pažadai su async/await laukimo

2017 m. Paskelbta ECMAScript versija pristatė asinchroninių funkcijų sintaksės palaikymą. async ir await galite asinchroniškai rašyti „sinchroninio stiliaus“. Šis kodas vis dar yra asinchroninis, tačiau jį lengviau skaityti / suprasti.

async/await pagrįstas pažadais: async funkcija visada grąžina pažadą. await „atlaisvinti“ pažadą, ir rezultatas, gautas pažado prasme, buvo išspręstas arba klaidą, jei pažadas buvo atmestas.

Svarbu žinoti: galite naudoti tik await async funkcijos. Šiuo metu, aukščiausiu lygiu, laukimas dar nepalaikomas, todėl gali reikėti, kad asinchroninis IIFE pradėtų async kontekstą.

Daugiau informacijos apie async galite sužinoti ir await MDN.

Čia pateikiamas pavyzdys, pagrįstas anksčiau pateiktu vėlavimu:

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

Dabartinės naršyklės ir priimančiosios palaikymo versijos async/await . Taip pat galite išlaikyti senesnes aplinkos, konvertuodami savo kodą į ES5 naudodami regeneratorių (arba įrankius, naudojančius regeneratorių, pvz., „ Babel“ ).


Leiskite funkcijoms priimti atšaukimus.

Atšaukimas yra tiesiog funkcija, perduota kitai funkcijai. Ši kita funkcija gali skambinti funkcijai, kuri praėjo, kai ji yra pasirengusi. Asinchroninio proceso kontekste atšaukimas bus iškviestas, kai bus vykdomas asinchroninis procesas. Paprastai rezultatas atsiunčiamas atgal.

Klausimo pavyzdyje galite priversti „ foo priimti atgalinį ryšį ir naudoti jį kaip success atšaukimą. Taigi tai

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

tampa

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

Čia apibrėžiame funkciją „inline“, bet galite perkelti bet kokią nuorodą į funkciją:

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

foo apibrėžiamas taip:

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

callback bus nuoroda į funkciją, kurią mes perduodame foo kai jį vadiname, ir mes tiesiog perduodame ją success . Ty kai tik „Ajax“ užklausa bus sėkminga, $.ajax callback ir $.ajax atsakymą į atgalinį ryšį (kuris gali būti nurodomas naudojant result , nes taip mes nustatėme atgalinį ryšį).

Taip pat galite apdoroti atsakymą prieš perduodant jį atgal:

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

Rašymo kodas, naudojant „callbacks“, yra lengviau nei atrodo. Galiausiai „JavaScript“ naršyklėje labai priklauso nuo įvykių (DOM renginių). „Ajax“ atsako gavimas yra tik įvykis.
Sunkumai gali kilti, kai turite dirbti su trečiosios šalies kodu, tačiau dauguma problemų gali būti išspręstos tiesiog mąstydami per programos srautą.


ES2015 +: pažadai ()

Promise API yra nauja ECMAScript 6 (ES2015) funkcija, tačiau ji jau turi gerą naršyklės palaikymą . Taip pat yra daug bibliotekų, kurios įgyvendina standartinius API pažadus ir suteikia papildomų metodų, kaip supaprastinti asinchroninių funkcijų naudojimą ir sudėtį (pavyzdžiui, „ bluebird“ ).

Pažadai yra ateities vertybių konteineriai. Kai pažadas gauna vertę (tai leidžiama) arba kai jis atšaukiamas (atmetamas), jis praneša visiems „klausytojams“, kurie nori pasiekti šią vertę.

Privalumai, palyginti su paprastais atšaukimais, yra tai, kad jie leidžia atskirti savo kodą ir juos lengviau kurti.

Čia pateikiamas paprastas pažadų pavyzdys:

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

Kalbant apie „Ajax“ skambutį, galėtume naudoti šiuos pažadus:

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

Visų privalumų, kuriuos jie žada pasiūlyti, aprašymas nepatenka į šio atsakymo sritį, tačiau, jei rašote naują kodą, turėtumėte rimtai apsvarstyti juos. Jie suteikia puikų abstrakcijos ir atskyrimo kodą.

Daugiau informacijos apie pažadus: HTML5 - uolos - „JavaScript“ pažadai

Pastaba: laukiami jQuery objektai

Atidėtas objektas yra individualus jQuery pažadų įgyvendinimas (prieš standartizuojant „Promise API“). Jie beveik elgiasi kaip pažadai, tačiau atskleidžia šiek tiek kitokią API.

Kiekvienas „jQuery Ajax“ metodas jau grąžina „laukiamą objektą“ (iš tikrųjų laukiančio objekto pažadą), kurį galite tiesiog grįžti iš savo funkcijos:

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

Pastaba: pažadas pasirodė

Atminkite, kad pažadai ir atidėti objektai yra tik ateities vertės konteineriai, o ne pati vertė. Pvz., Tarkime, kad turite:

 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 kodas klaidingai supranta minėtas asinchronines problemas. Konkrečiai, $.ajax() neužšaldo kodo, o tikrina „/ password“ puslapį jūsų serveryje - jis siunčia užklausą serveriui ir, laukdamas, nedelsdamas grąžina jQuery Ajax atidėtą objektą, o ne atsakymą iš serverio. Tai reiškia, kad if visada gauna šį atidėtą objektą, apdoros jį kaip true ir veiks taip, tarsi vartotojas būtų prisijungęs. Ne gera

Bet pataisykite ją lengvai:

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

Nerekomenduojama: sinchroniniai skambučiai „Ajax“

Kaip minėjau, kai kurios (!) Asinchroninės operacijos turi sinchroninius kolegas. Aš nepritariu jų naudojimui, tačiau, siekiant išsamumo, čia reikia atlikti sinchroninį skambutį:

Ne jQuery

Jei tiesiogiai naudojate XMLHTTPRequest objektą, eikite false kaip trečiąjį argumentą.

Jquery

Jei naudojate jQuery , galite nustatyti async parametrą kaip false . Atkreipkite dėmesį, kad ši parinktis nebenaudojama nuo jQuery 1.8. Tada vis tiek galite naudoti success atšaukimą arba prieigą prie jqXHR objekto responseText turinio :

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

Jei naudojate bet kurį kitą „jQuery Ajax“ metodą, pvz., $.getJSON , $.getJSON ir tt, turėtumėte pakeisti jį į $.ajax (nes galite tik perduoti konfigūracijos parametrus į $.ajax ).

Saugokitės Nepavyko atlikti sinchroninio JSONP užklausos. JSONP visada yra asinchroninis (kita priežastis, dėl kurios net nenagrinėjama ši galimybė).

5011
08 янв. Atsakymą pateikė Felix Kling 08 sausis 2013-01-08 20:06 '13, 20:06, 2013-01-08 20:06

Jei kode nenaudojate jQuery, šis atsakymas yra skirtas jums.

Jūsų kodas turėtų būti toks:

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

Felixas Klingas puikiai uždirbo atsakymą žmonėms, naudojantiems jQuery for AJAX, nusprendžiau suteikti alternatyvą žmonėms, kurie to nedaro.

( Atkreipkite dėmesį, kad tiems, kurie naudojasi nauju API fetch , kampiniu arba pažadais, pridėjau kitą atsakymą žemiau )


Ką jūs susiduriate

Tai trumpas kitos atsakymo „Problemos paaiškinimas“ santrauka, jei nesate tikri, perskaitę šį klausimą.

A iš AJAX reiškia asinchroninį . Tai reiškia, kad užklausos siuntimas (arba, atsakymo gavimas) pašalinamas iš įprastinio vykdymo srauto. Savo pavyzdyje .send grąžinamas nedelsiant, o kitas pranešimas return result; įvykdoma prieš funkciją, kurią išlaikėte kaip success atgalinį pašaukimą.

Tai reiškia, kad grįžus, jūsų nurodytas klausytojas dar neįvyko, o tai reiškia, kad grąžinta vertė nebuvo nustatyta.

Paprasta analogija

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

(Feed)

A grąžinimo reikšmė yra undefined , nes dalis a=5 dar nėra įvykdyta. AJAX tai daro, grąžinate vertę, kol serveris nesugebėjo pasakyti savo naršyklei, kokia vertė yra.

Vienas iš galimų šios problemos sprendimo būdų yra kodo pakartotinis panaudojimas, informuodamas savo programą apie tai, ką daryti atlikus skaičiavimą.

 function onComplete(a){ // When the code completes, do this alert(a); } function getFive(whenDone){ var a; setTimeout(function(){ a=5; whenDone(a); },10); } 

Tai vadinama CPS . Iš esmės, mes getFive veiksmui, kurį reikia atlikti, kai jis bus baigtas, mes nurodome kodui, kaip reaguoti, kai baigsis įvykis (pvz., Mūsų AJAX skambutis arba šiuo atveju laikas).

Naudoti:

 getFive(onComplete); 

Kuris turėtų įspėti „5“ ekrane. (Fiddle) .

Galimi sprendimai

border=0

Iš esmės yra du būdai išspręsti šią problemą:

  • Sudarykite AJAX skambutį sinchroniškai (skambinkite jam SJAX).
  • Pertvarkykite savo kodą, kad jis tinkamai veiktų su atšaukimais.

1. Sinchroninis AJAX - nedarykite to !!

Kaip sinchroninis AJAX, to nedarykite! „Felix“ atsakymas suteikia keletą tvirtų argumentų, kodėl tai yra bloga idėja. Apibendrinant, jis užšaldys vartotojo naršyklę, kol serveris grąžins atsakymą ir sukuria labai blogą vartotojo sąsają. Čia yra dar viena MDN santrauka, kodėl:

XMLHttpRequest palaiko sinchroninį ir asinchroninį ryšį. Tačiau apskritai asinchroniniai prašymai turėtų būti geriau nei sinchroniniai prašymai dėl veikimo priežasčių.

Trumpai tariant, sinchroniniai prašymai blokuoja kodo vykdymą ...... tai gali sukelti rimtų problemų ...

Jei norite tai padaryti, galite perduoti vėliavą: Štai kaip:

 var request = new XMLHttpRequest(); request.open('GET', 'yourURL', false); // `false` makes the request synchronous request.send(null); if (request.status === 200) {// That HTTP for 'ok' console.log(request.responseText); } 

2. Restruktūrizavimo kodeksas

Leiskite savo funkcijai priimti atgalinį ryšį. Pavyzdyje foo kodas gali būti priimtas, kad būtų priimtas atgalinis ryšys. Mes pasakysime kodą, kaip reaguoti, kai baigsis foo .

Taigi:

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

tampa:

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

Čia mes išlaikėme anoniminę funkciją, tačiau galėjome tiesiog perduoti nuorodą į esamą funkciją, tokiu būdu padarydami tai:

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

Norėdami gauti daugiau informacijos apie tai, kaip šis atšaukimo tipas yra įvykdytas, patikrinkite Felix atsakymą.

Dabar nustatykime foo, kad galėtume atitinkamai veikti

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

(smuikas)

Dabar mūsų „foo“ funkcija priima veiksmą, kuris prasideda, kai AJAX sėkmingai užbaigia, tai galime tęsti patikrindami, ar atsako būsena 200 reaguoja ir veikia atitinkamai (sukurkite klaidų tvarkyklę ir tt). Efektyvus mūsų problemos sprendimas.

Jei vis tiek kyla problemų dėl to, perskaitykite „AJAX“ pradžios vadovą MDN.

953
30 мая '13 в 2:30 2013-05-30 02:30 Benjamino Gruenbaumo atsakymas Gegužės 30 d., 13 val. 2:30 2013-05-30 02:30

XMLHttpRequest 2 (Pirmiausia perskaitykite Benjamin Grünbaum ir Felix Kling atsakymus)

Jei nenaudojate „jQuery“ ir norite gauti gražią trumpą XMLHttpRequest 2, kuri veikia šiuolaikinėse naršyklėse, taip pat naršyklėse, siūlau jį naudoti taip:

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

Kaip matote:

  1. Tai trumpesnė nei visos kitos išvardytos funkcijos.
  2. Grįžtamasis ryšys nustatomas tiesiogiai (todėl nereikalingi nereikalingi uždarymai).
  3. Yra keletas kitų situacijų, kurių nepamiršiu, kad XMLHttpRequest 1 erzina.

Yra du būdai gauti atsakymą į šį „Ajax“ skambutį (trys naudojant XMLHttpRequest kintamojo pavadinimą):

Paprasčiausias:

 this.response 

Arba, jei dėl kokių nors priežasčių bind() atgalinį ryšį su klase:

 e.target.response 

Pavyzdys:

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

Arba (anksčiau nurodytos geresnės anoniminės funkcijos visada yra problema):

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

Nėra nieko lengviau.

Dabar kai kurie žmonės tikriausiai pasakys, kad geriau naudoti onreadystatechange arba net XMLHttpRequest kintamojo pavadinimą. Tai neteisinga.

Naršykite pažangias XMLHttpRequest funkcijas

Visos modernios naršyklės yra palaikomos. Ir galiu patvirtinti, kad naudoju šį metodą, nes yra XMLHttpRequest 2. Aš niekada neturėjau jokių problemų visose naudojamose naršyklėse.

onreadystatechange naudinga tik tada, jei norite gauti antraštes 2 būsenoje.

XMLHttpRequest kintamojo pavadinimo naudojimas yra dar viena didelė klaida, nes jums reikia paskambinti viduje „onload“ arba „oreadystatechange“ uždarymuose, kitaip jūs jį praradote.


Dabar, jei norite ką nors sudėtingiau, naudodamiesi paštu ir formatu, galite lengvai išplėsti šią funkciją:

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

Vėlgi ... tai yra labai trumpa funkcija, tačiau ji gauna ir skelbia.

Naudojimo pavyzdžiai:

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

Arba perduokite visą formos elementą ( document.getElementsByTagName('form')[0] ):

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

Arba nustatykite kai kurias pasirinktines reikšmes:

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

Kaip matote, aš neįgyvendinau sinchronizavimo ... tai blogai.

Tai pasakęs ... kodėl gi ne tai padaryti paprastu būdu?


Kaip minėta pastaboje, klaidos sinchroninis naudojimas visiškai pažeidžia atsakymo prasmę. Kas yra geras trumpas būdas tinkamai naudoti „Ajax“?

Klaidų tvarkytojas

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

Pirmiau pateiktame scenarijuje turite klaidų tvarkyklę, kuri yra statiškai apibrėžta, todėl ji nepažeidžia funkcijos. Klaidų tvarkytojas gali būti naudojamas kitoms funkcijoms.

Но чтобы действительно вывести ошибку, единственный способ - написать неправильный URL, и в этом случае каждый браузер выдает ошибку.