Как да се върне отговор от асинхронно повикване?

Имам функция foo която прави Ajax заявка. Как да се върне отговорът от foo ?

Опитах се да върна стойност от обратната връзка за success , а също и да присвоя отговор на локална променлива във функцията и да я върна, но никой от тези методи не връща отговор.

 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 янв. Феликс Клинг от 08 януари 2013-01-08 20:06 '13 в 20:06 2013-01-08 20:06
@ 39 отговора
  • 1
  • 2

→ За по-общо обяснение на асинхронното поведение в различни примери вижте Защо моята променлива не се променя след като я променя във функция? - асинхронна връзка към кода

→ Ако вече сте разбрали проблема, отидете на възможните решения по-долу.

Този проблем

А в Аякс означава асинхронен . Това означава, че изпращането на заявка (или по-скоро получаване на отговор) се премахва от нормалния поток на изпълнение. Във вашия пример, $.ajax връща незабавно, а следващият return result; израз return result; , се изпълнява преди функцията, която сте прехвърлили като success извикване на success дори е извикана.

Ето една аналогия, която, надяваме се, изяснява разликата между синхронния и асинхронния поток:

синхронен

Представете си, че се обаждате на приятел и го молите да намери нещо за вас. Въпреки че може да отнеме известно време, изчакайте по телефона и погледнете в космоса, докато вашият приятел ви даде желания от вас отговор.

Същото се случва, когато правите повикване за функция, което съдържа "нормален" код:

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

Дори ако findItem да отнеме много време за изпълнение на findItem , всеки код, който следва var item = findItem(); трябва да изчакате, докато функцията върне резултат.

асинхронни

Пак се обаждате на приятеля си по същата причина. Но този път му кажете, че бързате и той трябва да ви се обади на мобилния си телефон. Затвориш, напусни къщата и направи всичко, което си планирал. Веднага щом приятелят ви се обади обратно, ще разгледате информацията, която ви е дал.

Точно това се случва, когато правите Ajax заявка.

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

Вместо да чака отговор, изпълнението продължава незабавно и операторът се изпълнява след повикване от Аякс. За да получите в крайна сметка отговор, предоставяте функция, която ще бъде извикана след получаване на отговора, обратно извикване (забележете нещо, „извикване“). Всяко изявление след това повикване се изпълнява преди повикването за обратно повикване.


Решение (и)

Приемете асинхронния характер на javascript! Въпреки че някои асинхронни операции осигуряват синхронни копия (като "Ajax"), те обикновено не се препоръчват за употреба, особено в контекста на браузъра.

Защо е толкова лошо, питаш?

JavaScript се изпълнява в нишката на потребителския интерфейс на браузъра, а всеки продължителен процес блокира потребителския интерфейс, което го прави неотзивчив. Освен това за JavaScript има горна граница на изпълнение, а браузърът ще поиска от потребителя дали да продължи изпълнението или не.

Всичко това наистина е лош потребителски опит. Потребителят няма да може да каже дали всичко работи правилно или не. Освен това ефектът ще бъде по-лош за потребителите с бавна връзка.

След това разглеждаме три различни решения, които са изградени един върху друг:

  • Обещания с async/await await (ES2017 +, налични в по-стари браузъри, ако използвате транспортер или регенератор)
  • Обратни обаждания (популярни в сайта)
  • Обещания с then() (ES2015 +, налични в по-стари браузъри, ако използвате една от многото обещаващи библиотеки)

И трите са налични в текущите браузъри и възел 7+.


ES2017 +: обещания с async/await изчакване

Издадената през 2017 г. версия ECMAScript въведе синтактична поддръжка за асинхронни функции. С async и await можете да пишете асинхронно в "синхронен стил". Кодът е все още асинхронен, но по-лесен за четене / разбиране.

async/await основава на обещания: функцията async винаги връща обещание. await "разгъва" обещанието и или резултатът в смисъла на обещанието беше решен с или дава грешка, ако обещанието беше отхвърлено.

Важно: Можете да използвате await в рамките на async функция. В момента, на най-високо ниво, await още не се поддържа, така че може да се наложи да накарате асинхронния IIFE да започне контекста на async .

Можете да прочетете повече за async и да await на MDN.

Ето пример, който се основава на закъснението по-горе:

 // 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 . Можете също да поддържате по-стари среди, като конвертирате кода си в ES5 с помощта на регенератор (или инструменти, използващи регенератор, като Babel ).


Нека функциите приемат обратни обаждания.

Обратното извикване е просто функция, предадена на друга функция. Тази друга функция може да извика дадена функция, когато е готова. В контекста на един асинхронен процес, извикването ще се извиква всеки път, когато се изпълни асинхронен процес. Обикновено резултатът се изпраща на обратното повикване.

В примера на въпроса можете да принудите foo да приеме обратно извикване и да го използвате като обратно извикване за success . Така че това

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

Тя се превръща

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

Тук дефинираме функцията "inline", но можете да препращате всяка препратка към функцията:

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

foo се дефинира както следва:

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

callback ще се отнася за функцията, която предаваме на foo когато я наричаме, и ние просто я прехвърляме към success . Т.е. веднага след като заявката на Ajax бъде успешна, $.ajax ще извика callback и ще $.ajax отговор на обратно извикване (което може да бъде реферирано с result , тъй като това е начинът, по който дефинираме обратно извикване).

Можете също така да обработите отговора, преди да го препратите към обратно извикване:

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

Писането на код с обратни обаждания е по-лесно, отколкото може да изглежда. В крайна сметка, JavaScript в браузъра е силно зависим от събития (DOM събития). Получаването на отговор от Аякс не е нищо повече от събитие.
Трудности могат да възникнат, когато трябва да работите с код на трета страна, но повечето проблеми могат да бъдат решени чрез просто мислене на потока на приложението.


ES2015 +: обещания с това ()

Promise API е нова функция на ECMAScript 6 (ES2015), но вече има добра поддръжка на браузъра . Има и много библиотеки, които прилагат стандартните API обещания и предоставят допълнителни методи за опростяване на използването и съставянето на асинхронни функции (например bluebird ).

Обещанията са контейнери за бъдещи стойности. Когато обещанието получи стойност (позволено) или когато бъде отменено (отхвърлено), то уведомява всички свои „слушатели“, които искат да получат достъп до тази стойност.

Предимството пред простите обаждания е, че те ви позволяват да разделите кода си и е по-лесно да ги композирате.

Ето един прост пример за използване на обещания:

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

По отношение на нашия Ajax повикване, можем да използваме следните обещания:

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

Описание на всички ползи, които обещават да предложат, е извън обхвата на този отговор, но ако пишете нов код, трябва сериозно да ги обмислите. Те осигуряват отлична абстракция и разделяне на вашия код.

Повече информация за обещанията: HTML5 - скали - обещания за JavaScript

Забележка: jQuery предстоящи обекти

Отложени обекти са персонализирано изпълнение на обещания в jQuery (преди стандартизацията на API на Promise). Те почти се държат като обещания, но излагат малко по-различен API.

Всеки метод на jQuery Ajax вече връща "чакащ обект" (всъщност обещание на предстоящ обект), който можете просто да върнете от неговата функция:

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

Забележка: обещанието се оказа

Не забравяйте, че обещанията и отложените обекти са просто контейнери за бъдеща стойност, а не самата стойност. Да предположим например, че имате следното:

 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 } 

Този код не разбира по-горе асинхронните проблеми. По-специално, $.ajax() не замразява кода, докато проверява страницата '/ password' на вашия сървър - изпраща заявка към сървъра и, докато изчаква, незабавно връща обекта jQuery Ajax Deferred, а не отговора от сървъра. Това означава, че if винаги ще получава този отложен обект, ще го обработва като true и ще действа така, както ако потребителят е влязъл. Не е добре

Но го поправете лесно:

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

Не се препоръчва: синхронни повиквания "Ajax"

Както казах, някои (!) Асинхронни операции имат синхронни колеги. Не препоръчвам тяхното използване, но за пълнота, ето как трябва да извършите синхронно обаждане:

Няма jQuery

Ако директно използвате обекта .open false като третия аргумент .open .

JQuery

Ако използвате jQuery , можете да зададете параметъра async на false . Моля, обърнете внимание, че тази опция е отхвърлена след jQuery 1.8. Тогава все още можете да използвате функцията за обратно извикване на success или достъп до свойството responseText на обекта jqXHR :

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

Ако използвате друг метод на jQuery Ajax, като $.get , $.getJSON и т.н., трябва да го промените на $.ajax (тъй като можете да предавате конфигурационни параметри само на $.ajax ).

Внимавай Не може да се извърши синхронно JSONP заявка. JSONP е винаги асинхронен в природата (още една причина да не се обмисля дори тази опция).

5011
08 янв. Отговорът е даден от Felix Kling Jan 08 2013-01-08 20:06 '13 в 20:06 2013-01-08 20:06

Ако не използвате jQuery в кода си, този отговор е за вас.

Кодът ви трябва да е по следния начин:

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

Феликс Клинг свърши чудесна работа, като написа отговора за хората, използващи jQuery за AJAX, реших да предложа алтернатива на хората, които не го правят.

( Моля, обърнете внимание, че за онези, които използват новото fetch API, ъглови или обещания, добавих друг отговор по-долу )


Какво срещате

Това е кратко резюме на "Обяснение на проблема" от друг отговор, ако не сте сигурни, след като прочетете това, прочетете това.

А в AJAX означава асинхронен . Това означава, че изпращането на заявка (или по-скоро получаване на отговор) се премахва от нормалния поток на изпълнение. В примера си .send връща незабавно, а следващият return result; извлечение return result; се изпълнява преди функцията, която сте прехвърлили като success извикване на success дори е била извикана.

Това означава, че когато се върнете, слушателят, който сте посочили, все още не е изпълнен, което означава, че стойността, която връщате, не е определена.

Проста аналогия

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

(Цигулка)

Връщаната стойност на a е undefined , тъй като частта a=5 все още не е изпълнена. AJAX прави това, вие връщате стойността, преди сървърът да може да каже на браузъра каква е стойността.

Едно възможно решение на този проблем е да се използва повторно кодът, информирайки вашата програма какво да прави, когато изчислението е завършено.

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

Това се нарича CPS . По принцип, ние getFive действието, което трябва да се изпълни, когато го завърши, ние казваме на нашия код как да реагира, когато събитието завърши (например, нашия AJAX повикване или в този случай изчакване).

Употреба:

 getFive(onComplete); 

Което трябва да предупреди "5" на екрана. (Цигулка) .

Възможни решения

border=0

Има два основни начина за решаване на този проблем:

  • Направете едно AJAX повикване синхронно (наречете го SJAX).
  • Преструктурирайте кода си, за да работи правилно с обратни обаждания.

1. Синхронно AJAX - не го правете !!

Що се отнася до синхронния AJAX, не го правете! Отговорът на Феликс дава някои силни аргументи за това, че това е лоша идея. За да обобщим, той ще замрази браузъра на потребителя, докато сървърът върне отговор и създаде много лош потребителски интерфейс. Ето още едно резюме на MDN:

XMLHttpRequest поддържа както синхронна, така и асинхронна комуникация. Като цяло обаче асинхронните заявки трябва да са за предпочитане пред синхронните заявки поради причини за производителност.

Накратко, синхронни заявки за изпълнение на код на код ...... това може да предизвика сериозни проблеми ...

Ако трябва да направите това, можете да подадете знамето: Ето как:

 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. Кодекс за преструктуриране

Нека вашата функция приеме обратно извикване. В примера, кодът foo може да бъде приет за обратно извикване. Ще кажем на нашия код как да реагира, когато foo завърши.

Така че:

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

става:

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

Тук преминахме анонимна функция, но можем просто да прехвърлим връзка към съществуваща функция, като я направим така:

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

За повече информация за това как се изпълнява този тип обратна връзка, проверете отговора на Felix.

Сега нека дефинираме foo за да действаме по съответния начин

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

(Цигулка)

Сега нашата функция foo приема действие, което започва, когато AJAX завърши успешно, можем да продължим с това, като проверим дали състоянието на отговор 200 отговаря и действа съответно (създайте манипулатор за грешки и т.н.). Ефективно решение на нашия проблем.

Ако все още имате проблеми с разбирането на това, прочетете AJAX Начално ръководство в MDN.

953
30 мая '13 в 2:30 2013-05-30 02:30 Отговор, даден от Бенджамин Груенбаум 30 май '13 в 2:30 2013-05-30 02:30

XMLHttpRequest 2 (Първо прочетете отговорите на Бенджамин Грюнбаум и Феликс Клинг )

Ако не използвате jQuery и искате да получите хубав кратък XMLHttpRequest 2, който работи в модерни браузъри, както и в мобилни браузъри, предлагам да го използвате както следва:

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

Както можете да видите:

  1. Тя е по-къса от всички други изброени функции.
  2. Обратното повикване се установява директно (следователно няма ненужни ненужни затваряния).
  3. Има и други ситуации, които не си спомням, че правят XMLHttpRequest 1 дразнещ.

Има два начина да получите отговор на този Ajax повикване (три, използвайки името на променливата XMLHttpRequest):

Най-простият:

 this.response 

Или, ако по някаква причина bind() обратно извикване с клас:

 e.target.response 

например:

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

Или (горните по-добри анонимни функции винаги са проблем):

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

Няма нищо по-лесно.

Сега някои хора вероятно ще кажат, че е по-добре да използват onreadystatechange или дори името на променливата XMLHttpRequest. Това е погрешно.

Разгледайте разширените функции на XMLHttpRequest

Поддържат се всички модерни браузъри. И мога да потвърдя, че използвам този подход, защото има XMLHttpRequest 2. Никога не съм имал проблеми във всички браузъри, които използвам.

onreadystatechange е полезен само ако искате да получите заглавията в състояние 2.

Използването на името на променливата XMLHttpRequest е друга голяма грешка, защото трябва да извикате вътре в onload / oreadystatechange затваряне, в противен случай сте го загубили.


Сега, ако искате нещо по-сложно, използвайки пост и FormData, можете лесно да разширите тази функция:

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

Отново ... това е много кратка функция, но тя получава и публикува.

Примери за използване:

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

Или преминете пълния елемент на формуляра ( document.getElementsByTagName('form')[0] ):

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

Или задайте някои персонализирани стойности:

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

Както можете да видите, не осъществих синхронизация ... това е лошо.

Като казах това ... защо не го направите по прост начин?


Както е споменато в коментара, използването на грешка синхронно напълно нарушава смисъла на отговора. Какво е добър кратък начин да използвате Ajax правилно?

Обработчик на грешки

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

В горния скрипт имате обработващ грешка, който е статично дефиниран, така че не компрометира функцията. Обработващият грешка може да се използва за други функции.

Но за да получите грешката, единственият начин е да напишете грешен URL адрес, като в този случай всеки браузър дава грешка.

Обработващите грешки могат да бъдат полезни, ако зададете персонализирани заглавки, задайте тип responseType за буфер с матрица или нещо друго ...

Дори ако предавате POSTAPAPAP като метод, той няма да даде грешка.

Дори ако прехвърлите 'fdggdgilfdghfldj' като данни за формуляри, то няма да даде грешка.

В първия случай грешката е вътре в this.statusText displayAjax() в този this.statusText като Method not Allowed .