return result;
Для более общего объяснения поведения успеха с dfunction findItem () {var item; while (item_not_found) {// search} return item; } var item = findItem (); // Делаем что-то с элементом doSomethingElse (); см. раздел Почему моя переменная неизменна после того, как я изменяю ее внутри функции? - findItemhronous code reference
var item = findItem();
Если вы уже понимаете проблему, перейдите к возможным решениям ниже.
Проблема
В Ajax означает асинхронный / awaithronous . Это означает, что отправка запроса (или, скорее, получение ответа) вынимается из обычного потока выполнения. В вашем примере возвращается немедленно и следующий оператор ,, выполняется до того, как функция, которую вы передали, так как ожидание было даже вызвано.then()
async/await
async
Вот аналогия, которая, мы надеемся, делает разницу между синхронным и асинхронным / awaithronous flow clearer:
синхронный
Представьте, что вы позвоните другу и попросите его посмотреть что-нибудь для вас. Хотя это может занять некоторое время, вы ждете по телефону и смотрите в космос, пока ваш друг не даст вам ответ, который вам нужен.
То же самое происходит, когда вы вызываете вызов функции, содержащий «нормальный» код:
async
Несмотря на то, что это await
может занять много времени, любой следующий код await
должен ждать, пока функция вернет асинхронный вызов.
асинхронный
Вы снова звоните своему другу по той же причине. Но на этот раз ты скажешь ему, что ты спешишь, и он должен перезвонить тебе на свой мобильный телефон. Вы вешаете трубку, выходите из дома и делаете все, что планируете делать. Как только ваш друг перезвонит вам, вы имеете дело с информацией, которую он вам дал.
Это именно то, что происходит, когда вы делаете запрос Ajax.
await
Вместо того, чтобы ждать ответа, выполнение продолжается немедленно, и оператор после вызова Ajax выполняется. Чтобы получить ответ в конце концов, вы предоставляете функцию, которая будет вызываться после получения ответа, обратного вызова (обратите внимание на что-то? Обратный вызов ?). Любое утверждение, поступившее после этого вызова, выполняется до вызова обратного вызова.
Решение (s)
Объявите асинхронный характер JavaScript! Хотя определенные асинхронные операции предоставляют синхронные копии (так же как и Ajax), обычно их не рекомендуется использовать, особенно в контексте браузера.
Почему ты плохо спрашиваешь?
JavaScript работает в потоке пользовательского интерфейса браузера, и любой длительный процесс блокирует пользовательский интерфейс, делая его невосприимчивым. Кроме того, существует верхний предел времени выполнения JavaScript, и браузер запрашивает у пользователя, продолжать ли это выполнение или нет.
Все это очень плохой пользовательский интерфейс. Пользователь не сможет сказать, все ли работает нормально или нет. Кроме того, эффект будет хуже для пользователей с медленным подключением.
Ниже мы рассмотрим три разных решения, которые все строятся друг над другом:
- Обещает с
// 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; } } // Async functions always return a promise getAllBooks() .then(function(books) { console.log(books); });
(ES2017 +, доступный в старых браузерах, если вы используете транспилер или регенератор) - Обратные вызовы (популярные в узле)
- Обещает
async/await
(ES2015 +, доступный в старых браузерах, если вы используете одну из многих библиотек обещаний)
Все три доступны в текущих браузерах, а узел 7+.
ES2017 +: Обещает с foo
В новой версии ECMAScript, выпущенной в 2017 году, появилась поддержка синтаксического уровня для асинхронных функций. С помощью async
и success
вы можете писать асинхронно в «синхронном стиле». Не ошибитесь: код по-прежнему асинхронен, но его легче читать / понимать.
var result = foo(); // Code that depends on 'result'
основывается на обещаниях: async
функция всегда возвращает обещание. foo(function(result) { // Code that depends on 'result' });
«разворачивает» обещание и либо приводит к тому, что обещание было разрешено, либо порождает ошибку, если обещание было отклонено.
Важно: вы можете использовать только function myCallback(result) { // Code that depends on 'result' } foo(myCallback);
внутри async
функции. Это означает, что на самом верхнем уровне вам все равно придется работать непосредственно с обещанием.
Вы можете узнать больше о MDN async
и function foo(callback) { $.ajax({ // ... success: callback }); }
о нем.
Вот пример, который основывается на верхней задержке выше:
callback
Поддержка новых браузеров и узловfoo
. Вы также можете поддерживать устаревшие среды, преобразовывая свой код в ES5 с помощью регенератора (или инструментов, которые используют регенератор, например, Babel ).
Пусть функции принимают обратные вызовы
Обратный вызов - это просто функция, переданная другой функции. Эта другая функция может вызывать функцию, передаваемую всякий раз, когда она будет готова. В контексте асинхронного процесса обратный вызов будет вызываться всякий раз, когда выполняется асинхронный процесс. Обычно результат передается на обратный вызов.
В примере вопроса вы можете success
принять обратный вызов и использовать его в качестве $.ajax
обратного вызова. Итак, это
callback
становится
result
Здесь мы определили функцию «inline», но вы можете передать любую ссылку на функцию:
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
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). });
сам определяется следующим образом:
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
});
callback
будет ссылаться на функцию, к которой мы переходим, 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 равен $ .ajax (), if
вызовет callback
и передаст ответ на обратный вызов (на который можно ссылаться result
, поскольку именно так мы определили обратный вызов).
Вы также можете обработать ответ, прежде чем передавать его на обратный вызов:
true
Легче писать код с помощью обратных вызовов, чем может показаться. В конце концов, JavaScript в браузере сильно управляется событиями (события DOM). Получение ответа Ajax - это не что иное, как событие.
Трудности могут возникнуть, когда вам придется работать со сторонним кодом, но большинство проблем можно решить, просто продумав поток приложений.
ES2015 +: Обещает с помощью checkPassword () .done (function (r) {if (r) {// Сообщите пользователю, что они вошли в систему} else {// Сообщите пользователю, что у них плохой пароль}}) .fail (function ( x) {// Расскажите, что произошло что-то плохое}); ()
Promise API является новой функцией ECMAScript 6 (ES2015), но он имеет хорошую поддержку браузера уже. Существует также множество библиотек, которые реализуют стандартный API Promises и предоставляют дополнительные методы для облегчения использования и составления асинхронных функций (например, bluebird ).
Обещания - это контейнеры для будущих значений. Когда обещание получает значение (оно разрешено ) или когда оно отменено ( отклонено ), оно уведомляет всех своих «слушателей», которые хотят получить доступ к этому значению.
Преимущество перед обычными обратными вызовами заключается в том, что они позволяют вам развязывать код, и их легче создавать.
Вот простой пример использования обещания:
XMLHTTPRequest
Применительно к нашему призыву Ajax мы могли бы использовать такие обещания:
false
Описывая все преимущества, которые предлагают обещания, выходит за рамки этого ответа, но если вы пишете новый код, вам следует серьезно их рассмотреть. Они обеспечивают отличную абстракцию и разделение вашего кода.
Дополнительная информация о обещаниях: камни HTML5 - JavaScript Promises
Сторона примечания: отложенные объекты jQuery
Отложенные объекты представляют собой пользовательскую реализацию обещаний jQuery (до того, как API Promise был стандартизован). Они ведут себя почти как обещания, но демонстрируют несколько иной API.
Каждый метод Ajax jQuery уже возвращает «отложенный объект» (фактически обещание отложенного объекта), который вы можете просто вернуть из своей функции:
.open
Сторона примечания: Promise gotchas
Имейте в виду, что обещания и отложенные объекты - это просто контейнеры для будущего значения, они не являются самим значением. Например, предположим, что у вас было следующее:
async
Этот код неправильно понимает вышеупомянутые проблемы асинхронности. В частности, false
он не блокирует код при проверке страницы «/ password» на вашем сервере - он отправляет запрос на сервер и, пока он ждет, немедленно возвращает объект JQuery Ajax Deferred, а не ответ с сервера. Это означает, что if
оператор всегда будет получать этот объект «Отложен», рассматривать его как success
и продолжать действовать, как если бы пользователь вошел в систему. Не хорошо.
Но исправление легко:
responseText
Не рекомендуется: Синхронные вызовы «Ajax»
Как я уже упоминал, некоторые (!) Асинхронные операции имеют синхронные копии. Я не сторонник их использования, но для полноты, вот как вы могли бы выполнить синхронный вызов:
Без jQuery
Если вы напрямую используете $.get
объект, передайте его в $.getJSON
качестве третьего аргумента $.ajax
.
JQuery
Если вы используете jQuery , вы можете установить этот async
параметр $.ajax
. Обратите внимание, что этот параметр устарел после jQuery 1.8. Вы можете использовать foo () {var httpRequest = new XMLHttpRequest (); httpRequest.open ('GET', "/ echo / json"); httpRequest.send (); return httpRequest.responseText; } var result = foo (); // всегда заканчивается тем, что «undefined» либо использует fetch
обратный вызов, либо получает доступ к .send
свойству объекта jqXHR :
return result;
Если вы используете любой другой метод jQuery Ajax, такой как success
, function getFive(){ var a; setTimeout(function(){ a=5; },10); return a; }
и т. Д., Вы должны его изменить a
(поскольку вы можете передавать только параметры конфигурации undefined
).
Берегись! Невозможно выполнить синхронный запрос JSONP . JSONP по самой своей природе всегда асинхронен (еще одна причина не учитывать этот вариант).