Paano ibabalik ang tugon mula sa isang asynchronous na tawag?

Mayroon akong foo function na gumagawa ng isang kahilingan ng Ajax. Paano ibabalik ang sagot mula sa foo ?

Sinubukan kong ibalik ang isang halaga mula sa callback ng success , at italaga din ang tugon sa isang lokal na variable sa loob ng function at ibalik ito, ngunit wala sa mga pamamaraan na ito ang nagbabalik ng tugon.

 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 янв. Itinakda ni Felix Kling ang Enero 08 2013-01-08 20:06 '13 sa 20:06 2013-01-08 20:06
@ 39 sagot
  • 1
  • 2

→ Para sa isang mas pangkalahatang paliwanag ng asynchronous na pag-uugali sa iba't ibang mga halimbawa, tingnan ang. Bakit ang aking variable ay hindi nagbabago pagkatapos kong baguhin ito sa loob ng isang function? - Asynchronous na link sa code

→ Kung naintindihan mo na ang problema, pumunta sa mga posibleng solusyon sa ibaba.

Ang problemang ito

A sa Ajax ay nangangahulugang asynchronous . Nangangahulugan ito na ang pagpapadala ng isang kahilingan (o, sa halip, pagtanggap ng tugon) ay aalisin mula sa normal na daloy ng pagpapatupad. Sa iyong halimbawa, ang $.ajax bumalik kaagad, at ang susunod na statement ay return result; , ay isinasagawa bago ang function na naipasa mo bi>success tinatawag pa rin.

Narito ang isang pagkakatulad na, sana, tinutukoy ang pagkakaiba sa pagitan ng isang kasabay at asynchronous stream:

kasabay

Isipin na ikaw ay tumatawag sa isang kaibigan at hinihiling sa kanya na makahanap ng isang bagay para sa iyo. Bagaman maaaring tumagal ng i>

Ang parehong bagay ay mangyayari kapag gumawa ka ng isang function call na naglalaman ng "normal" na code:

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

Kahit na maaaring tumagal ng isang mahabang oras upang execute findItem , anumang code na sumusunod var item = findItem(); dapat maghintay hanggang ang function ay magbabalik ng isang resulta.

Asynchronous

Tinawagan mo muli ang iyong kaibigan para sa parehong dahilan. Ngunit sa oras na ito sasabihin mo sa kanya na ikaw ay nagmamadali, at dapat siyang tawagan muli sa iyong mobile phone. Nag-hang up ka, umalis sa bahay at gawin ang lahat ng iyong pinlano. Sa lalong madaling tawagin ka ng iyong kaibigan, haharapin mo ang impormasyong ibinigay niya sa iyo.

Ito ay eksakto kung ano ang mangyayari kapag gumawa ka ng kahilingan ng Ajax.

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

Sa halip na maghintay para sa isang tugon, pagpapatupad ay patuloy kaagad at ang pahayag ay papatayin pagkatapos ng isang tawag na Ajax. Upang makatanggap ng tugon sa huli, nagbibigay ka ng isang function na tatawaging pagkatapos matanggap ang tugon, isang callback (tandaan ang isang bagay, "tumawag muli"). Ang anumang pahayag na sumusunod sa tawag na ito ay isinasagawa bago ang callback call.


Solusyon (s)

Magpatibay ng asynchronous na likas na katangian ng javascript! Kahit na ang i>

Bakit masama ito, nagtatanong ka?

Ang mga JavaScript ay tumatakbo sa thread ng browser ng browser, at sinasadya ng anumang mahahabang proseso ang user interface, na ginagawang hindi tumutugon. Bi>

Ang lahat ng ito ay talagang isang masamang karanasan ng gumagamit. Hindi masasabi ng user kung lahat ay gumagana nang maayos o hindi. Bi>

Susunod, tinitingnan namin ang tatlong iba't ibang mga solusyon na itinayo sa ibabaw ng bawat isa:

  • Mga pangako na may async/await (ES2017 +, magagamit sa mas lumang mga browser kung gumagamit ka ng isang transporter o isang regenerator)
  • Mga callbacks (sikat sa site)
  • Mga pangako na may then() (ES2015 +, na magagamit sa mas lumang mga browser kung gumagamit ka ng isa sa maraming mga aklatang pangako)

Lahat ng tatlong ay magagamit sa kasalukuyang mga browser at node 7+.


ES2017 +: pangako na may async/await naghihintay

Ang ECMAScript na bersyon na inilabas noong 2017 ay nagpasimula ng suporta sa syntax para sa mga asynchronous function. Sa async at await maaari mong isulat ang asynchronously sa "kasabay na estilo". Ang code ay pa rin asynchronous, ngunit mas madaling basahin / maintindihan.

async/await batay sa mga pangako: ang function ng async palaging nagbabalik ng isang pangako. await "pagbawas" ng pangako at alinman sa resulta sa kahulugan ng pangako ay nalutas sa o nagbibigay ng isang pagkakamali kung ang pangako ay tinanggihan.

Mahalaga: Maaari mo lamang gamitin ang await loob ng isang function na async . Sa ngayon, sa pinakamataas na antas, await ay hindi pa sinusuportahan, kaya maaaring kai>async .

Maaari kang magbasa nang higit pa tungkol sa async at await sa MDN.

Narito ang isang halimbawa na batay sa pagkaantala sa itaas:

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

Kasalukuyang mga bersyon ng browser at host support async/await . Maaari mo ring mapanatili ang mas lumang mga kapaligiran sa pamamagitan ng pag-convert ng iyong code sa ES5 gamit ang isang regenerator (o mga tool na gumagamit ng isang regenerator, tulad ng Babel ).


Hayaan tanggapin ang mga function callbacks.

Ang isang callback ay isang function na lamang sa isang function. Ang iba pang function na ito ay maaaring tumawag sa isang function na lumipas sa tuwing ito ay handa na. Sa konteksto ng isang asynchronous na proseso, isang callback ay tatawagan tuwing ang isang asynchronous na proseso ay naisakatuparan. Karaniwan ang resulta ay ipinadala sa callback.

Sa halimbawa ng tanong, maaari mong pilitin ang foo na tumanggap ng callback at gamitin ito bi>success . Kaya ito

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

ay nagiging

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

Narito naming tukuyin ang function na "inline", ngunit maaari mong ipasa ang anumang reference sa pag-andar:

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

foo mismo ay tinukoy bi>

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

callback ay tumutukoy sa function na ipinasa namin sa foo kapag tinawagan namin ito, at ipinapasa namin lamang ito sa success . Ibig sabihin sa lalong madaling matagumpay ang kahilingan ng Ajax, ang $.ajax ay tatawag sa callback at $.ajax tugon sa callback (na maaaring isangguni gamit ang result , dahil ito ay kung paano namin tinukoy ang callback).

Maaari mo ring iproseso ang tugon bago ipasa ito sa callback:

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

Ang pagsulat ng code gamit ang mga callbacks ay mas madali kaysa sa maaaring mukhang ito. Pagkatapos ng lahat, ang JavaScript sa browser ay lubos na nakadepende sa mga kaganapan (DOM events). Ang pagkuha ng isang tugon ng Ajax ay hindi higit sa isang kaganapan.
Maaaring lumitaw ang mga paghihirap kapag mayroon kang gumana sa ikatlong-partido na code, ngunit ang karamihan sa mga problema ay maaaring malutas sa pamamagitan lamang ng pag-iisip sa pamamagitan ng daloy ng aplikasyon.


ES2015 +: pangako na may pagkatapos ()

Ang pangako API ay isang bagong tampok ng ECMAScript 6 (ES2015), ngunit mayroon itong magandang suporta sa browser . Mayroon ding mga maraming mga aklatan na nagpapatupad ng mga pamantayan ng API na Mga Pangako at nagbibigay ng karagdagang mga pamamaraan upang gawing simple ang paggamit at komposisyon ng mga asynchronous function (halimbawa, bluebird ).

Ang mga pangako ay mga lalagyan para sa mga halaga sa hinaharap. Kapag ang isang pangako ay tumatanggap ng isang halaga (ito ay pinahihintulutan) o kapag kinansela (tinanggihan), ito ay nagpapaalam sa lahat ng mga "tagapakinig" na gustong ma-access ang halaga na ito.

Ang kalamangan sa mga simpleng callbacks ay pinahihintulutan ka nitong paghiwalayin ang iyong code, at mas madali itong magsulat.

Narito ang isang simpleng halimbawa ng paggamit ng mga pangako:

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

Tungkol sa aming tawag sa Ajax, maaari naming gamitin ang sumusunod na mga pangako:

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

Ang isang paglalarawan ng lahat ng mga benepisyo na ipinangako nila upang mag-alok ay lampas sa saklaw ng sagot na ito, ngunit kung nagsusulat ka ng isang bagong code, sineseryoso mong isaa>

Higit pang impormasyon sa mga pangako: HTML5 - mga bato - Mga pangako ng JavaScript

Tandaan: jQuery nakabinbing mga bagay

Ang mga ipinagpaliban na bagay ay isang pasadyang pagpapatupad ng mga pangako sa jQuery (bago ang standardisasyon ng Pangako API). Sila halos kumilos tulad ng mga pangako, ngunit ilantad ang isang bahagyang naiiba API.

Ang bawat paraan ng jQuery Ajax ay nagbabalik ng isang "nakabinbing bagay" (talagang isang pangako ng isang nakabinbing bagay), na maaari mong ibalik mula sa pag-andar nito:

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

Tandaan: ang pangako ay naka-out

Tandaan na ang mga pangako at mga ipinagpaliban na bagay ay mga lamang na lalagyan para sa hinaharap na halaga, hindi ang halaga mismo. Halimbawa, ipagpalagay na mayroon kang mga sumusunod:

 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 } 

Ang pagkaunawa ng code na ito sa mga problemang asynchronous sa itaas. Sa partikular, ang $.ajax() ay hindi nag-freeze ng code habang sinusuri ang pahina ng '/ password' sa iyong server - nagpapadala ito ng kahilingan sa server at, habang naghihintay, agad na ibabalik ang jQuery Ajax na ipinagpaliban na bagay, hindi ang tugon mula sa server. Nangangahulugan ito na if ay laging makatatanggap ng ipinagpaliban na bagay na ito, iproseso ito bi>true at kumilos na parang naka-log in ang user. Hindi maganda

Ngunit ayusin ito madali:

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

Hindi inirerekumenda: mga kasabay na tawag na "Ajax"

Tulad ng sinabi ko, ang i>

Wa>

Kung direkta mong ginagamit ang XMLHTTPRequest object, pumasa sa false bi>.open .

Jquery

Kung gumagamit ka ng jQuery , maaari mong itakda ang parameter na async sa false . Mangyaring tandaan na ang pagpipiliang ito ay hindi na ginagamit dahil sa jQuery 1.8. Pagkatapos ay maaari mo pa ring gamitin ang success callback o pag-access sa propertyText property ng jqXHR object :

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

Kung gumamit ka ng anumang ibang paraan ng jQuery Ajax, tulad ng $.get , $.getJSON , atbp, dapat mong baguhin ito sa $.ajax (dahil maaari mo lamang ipasa ang mga parameter ng configuration sa $.ajax ).

Watch out Hindi makagawa ng kasabay na kahilingan ng JSONP . Ang JSONP ay palaging asynchronous sa kalikasan (isa pang kadahilanan na hindi kahit na isaa>

5011
08 янв. Ang sagot ay ibinigay ni Felix Kling Jan 08 2013-01-08 20:06 '13 sa 20:06 2013-01-08 20:06

Kung hindi mo ginagamit ang jQuery sa iyong code, ang sagot na ito ay para sa iyo.

Ang iyong code ay dapat na isang bagay na katulad nito:

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

Ginawa ni Felix Kling ang isang mahusay na trabaho sa pagsulat ng sagot para sa mga taong gumagamit ng jQuery para sa AJAX, nagpasya akong magbigay ng alternatibo para sa mga taong hindi.

( Pakitandaan na para sa mga gumagamit ng bagong fetch API, Angular, o pangako, nagdagdag ako ng isa pang sagot sa ibaba )


Ano ang nakatagpo mo

Ito ay isang maikling buod ng "Paliwanag ng problema" mula sa ibang sagot; kung hindi ka sigurado, pagkatapos basahin ito, basahin ito.

Ang A sa AJAX ay nangangahulugang asynchronous . Nangangahulugan ito na ang pagpapadala ng isang kahilingan (o, sa halip, pagtanggap ng tugon) ay aalisin mula sa normal na daloy ng pagpapatupad. Sa iyong halimbawa, ang .send agad na ibinalik, at ang susunod na pahayag ay return result; ay naisakatuparan bago pa man tawagin ang function na naipasa mo bi>success .

Nangangahulugan ito na kapag bumalik ka, ang tagapakinig na tinukoy mo ay hindi pa natutupad, na nangangahulugan na ang halaga na iyong ibinalik ay hindi natukoy.

Simple pagkakatulad

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

(Feeddle)

Ang halaga ng pagbalik ng a ay hindi undefined , dahil ang bahagi a=5 ay hindi pa pinaandar. Ginagawa ito ng AJAX; ibinabalik mo ang halaga bago maipahayag ng server ang iyong browser kung ano ang halaga nito.

Ang isang posibleng solusyon sa problemang ito ay ang muling paggamit ng code, na nagpapaalam sa iyong programa tungkol sa kung ano ang gagawin kapag nakumpleto ang pagkalkula.

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

Ito ay tinatawag na CPS . Talaga, getFive namin ng getFive pagkilos na kai>

Gamitin ang:

 getFive(onComplete); 

Aling dapat alertuhan ang "5" sa screen. (Fiddle) .

Posibleng mga solusyon

border=0

May mga karaniwang dalawang paraan upang malutas ang problemang ito:

  • Gumawa ng isang tawag na AJAX kasabay (tumawag sa kanya SJAX).
  • I-reset ang iyong code upang gumana nang tama sa mga callbacks.

1. Kasabay ng AJAX - huwag gawin ito !!

Tulad ng para sa kasabay na AJAX, huwag gawin ito! Ang tugon ni Felix ay nagbibigay ng i>

Sinusuportahan ng XMLHttpRequest ang parehong kasabay at asynchronous na komunikasyon. Sa pangkalahatan, gayunpaman, ang mga kahilingan ng asynchronous ay dapat na lalong kanais-nais sa mga kasabay na kahilingan para sa mga dahilan ng pagganap.

Sa maikli, kasabay ng mga kahilingan ang block code pagpapatupad ...... ito ay maaaring maging sanhi ng malubhang problema ...

Kung kai>Narito kung paano:

 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. Code ng Restructuring

Hayaan ang iyong function na tanggapin ang callback. Sa halimbawa, ang foo code ay maaaring gawin upang tanggapin ang isang callback. Sasabihin namin sa aming code kung paano tumugon kapag natapos na ang foo .

Kaya:

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

nagiging:

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

Narito nagpasa kami ng isang hindi kila>

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

Para sa higit pang impormasyon kung paano isinagawa ang uri ng callback na ito, tingnan ang tugon ni Felix.

Ngayon, tukuyin natin ang foo mismo upang kumilos nang naaayon

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

(byolin)

Ngayon ang aming foo function ay tumatanggap ng isang aksyon na nagsisimula kapag matagumpay na nakumpleto ang AJAX, maaari naming ipagpatuloy ito sa pamamagitan ng pagsuri kung ang tugon na katayuan 200 ay tumutugon at kumikilos nang naaayon (gumawa ng isang error handler, atbp.). Isang epektibong solusyon sa aming problema.

Kung mayroon ka pa ring problema sa pag-unawa, basahin ang Gabay sa Pagsisimula ng AJAX sa MDN.

953
30 мая '13 в 2:30 2013-05-30 02:30 Sumagot na ibinigay ni Benjamin Gruenbaum Mayo 30, '13 sa 2:30 2013-05-30 02:30

XMLHttpRequest 2 (Unang basahin ang mga sagot mula kay Benjamin Grünbaum at Felix Kling )

Kung hindi ka gumagamit ng jQuery at nais mong makakuha ng isang magandang maikling XMLHttpRequest 2 na gumagana sa mga modernong browser, pati na rin sa mga mobile browser, iminumungkahi ko ang paggamit nito tulad ng sumusunod:

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

Tulad ng makikita mo:

  1. Ito ay mas maikli kaysa sa lahat ng iba pang mga function na nakalista.
  2. Ang callback ay itinatag nang direkta (samakatuwid, wa>
  3. May i>

Mayroong dalawang paraan upang makakuha ng sagot sa tawag na Ajax na ito (tatlong gamit ang pangalan ng variable XMLHttpRequest):

Ang pinakasimpleng:

 this.response 

O, kung sa isang kadahilanan ay bind() ka bind() callback sa isang klase:

 e.target.response 

Halimbawa:

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

O (ang mas mahusay na hindi nakikila>

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

Wa>

Ngayon ang i>

Galugarin ang mga advanced na tampok ng XMLHttpRequest

Ang lahat ng mga modernong browser ay sinusuportahan. At makukumpirma ko na ginagamit ko ang diskarte na ito dahil mayroong XMLHttpRequest 2. Wala akong anumang problema sa lahat ng mga browser na ginagamit ko.

Ang onreadystatechange ay kapaki-pakinabang lamang kung nais mong makuha ang mga header sa estado 2.

Ang paggamit ng XMLHttpRequest variable name ay isa pang malaking pagkakamali, dahil kai>


Ngayon, kung gusto mo ng isang bagay na mas kumplikado gamit ang post at FormData, madali mong mapalawak ang function na ito:

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

Muli ... ito ay isang maikling pagpapaandar, ngunit ito ay tumatanggap at nagpa-publish.

Mga halimbawa ng paggamit:

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

O ipasa ang kumpletong elemento ng form ( document.getElementsByTagName('form')[0] ):

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

O magtakda ng i>

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

Tulad ng iyong nakikita, hindi ko ipatupad ang pag-synchronize ... ito ay masama.

Sinabi na ... bakit hindi ito ginagawa sa isang simpleng paraan?


Tulad ng nabanggit sa komento, ang paggamit ng error kasabay ay ganap na lumalabag sa kahulugan ng sagot. Ano ang isang maikling maikling paraan upang magamit ang tama ng Ajax?

Error handler

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

Sa script sa itaas, mayroon kang isang handler ng error na tinukoy ng istatistika, kaya hindi ito nakompromiso ang pag-andar. Maaaring gamitin ang handler ng error para sa iba pang mga function.

Ngunit upang makakuha ng tunay na error, ang tanging paraan ay upang isulat ang maling URL, kung saan ang bawat browser ay nagbibigay ng isang error.

Maaaring maging kapaki-pakinabang ang mga handler ng error kung nagtakda ka ng mga custom na header, itakda ang responseType para sa isang blob array buffer o ibang bagay ...

Kahit na pumasa ka ng POSTAPAPAP bi>

Kahit na pumasa ka ng 'fdggdgilfdghfldj' bi>

В первом случае ошибка находится внутри displayAjax() в this.statusText как Method not Allowed .