Как вернуть ответ от асинхронного вызова?
У меня есть функция 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`.
- 1
- 2
→ Для более общего объяснения асинхронного поведения на разных примерах см. Почему моя переменная не изменяется после того, как я изменил ее внутри функции? - асинхронная ссылка на код
→ Если вы уже поняли проблему, перейдите к возможным решениям ниже.
Эта проблема
A в Ajax означает асинхронный. Это означает, что отправка запроса (или, вернее, получение ответа) исключается из обычного потока выполнения. В вашем примере $.ajax
возвращает сразу, а следующий оператор return result;
, выполняется до того, как функция, которую вы передали в качестве success
обратного вызова, даже была вызвана.
Вот аналогия, которая, надо надеяться, проясняет разницу между синхронным и асинхронным потоком:
синхронный
Представьте, что вы звоните другу и просите его найти что-то для вас. Хотя это может занять некоторое время, вы ждете по телефону и смотрите в пространство, пока ваш друг не даст вам ответ, который вам нужен.
То же самое происходит, когда вы делаете вызов функции, содержащий "нормальный" код:
function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}
var item = findItem();
// Do something with item
doSomethingElse();
Несмотря на то, что выполнение findItem
может занять много времени, любой код, следующий после var item = findItem();
должен ждать, пока функция не вернет результат.
Асинхронный
Вы звоните своему другу снова по той же причине. Но на этот раз вы говорите ему, что спешите, и он должен перезвонить вам на ваш мобильный телефон. Вы вешаете трубку, выходите из дома и делаете все, что планировали. Как только ваш друг перезвонит вам, вы будете иметь дело с информацией, которую он вам дал.
Это именно то, что происходит, когда вы делаете запрос Ajax.
findItem(function(item) {
// Do something with item
});
doSomethingElse();
Вместо ожидания ответа выполнение продолжается немедленно и выполняется оператор после вызова Ajax. Чтобы в конечном итоге получить ответ, вы предоставляете функцию, которая будет вызываться после получения ответа, обратный вызов (заметьте что-нибудь "перезвонить"). Любой оператор, следующий за этим вызовом, выполняется до вызова обратного вызова.
Решение (s)
Примите асинхронную природу JavaScript! Хотя некоторые асинхронные операции предоставляют синхронные аналоги (как и "Ajax"), обычно их не рекомендуется использовать, особенно в контексте браузера.
Почему это плохо, спросите вы?
JavaScript запускается в потоке пользовательского интерфейса браузера, и любой длительный процесс блокирует пользовательский интерфейс, что делает его неотзывчивым. Кроме того, существует верхний предел времени выполнения для JavaScript, и браузер спросит пользователя, продолжать ли выполнение или нет.
Все это действительно плохой пользовательский опыт. Пользователь не сможет сказать, все ли работает нормально или нет. Кроме того, эффект будет хуже для пользователей с медленным подключением.
Далее мы рассмотрим три различных решения, которые все строятся друг на друге:
- Обещания с
async/await
(ES2017+, доступно в старых браузерах, если вы используете транспортер или регенератор) - Обратные вызовы (популярные в узле)
- Обещания с помощью
then()
(ES2015+, доступно в старых браузерах, если вы используете одну из множества библиотек обещаний)
Все три доступны в текущих браузерах и узле 7+.
ES2017+: обещания с async/await
ожиданием
Версия ECMAScript, выпущенная в 2017 году, представила поддержку на уровне синтаксиса для асинхронных функций. С помощью async
и await
вы можете писать асинхронно в "синхронном стиле". Код все еще асинхронный, но его легче читать/понимать.
async/await
основывается на обещаниях: async
функция всегда возвращает обещание. await
"разворачивает" обещание и либо результат в значении обещание было решены с или выдает ошибку, если обещание было отклонено.
Важно: вы можете использовать await
только внутри async
функции. Прямо сейчас, на высшем уровне await
пока не поддерживается, так что вы, возможно, придется сделать асинхронной IIFE (Сразу Вызывается функция Expression), чтобы начать 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
и передаст ответ на обратный вызов (на который можно ссылаться с помощью result
, поскольку именно так мы определили обратный вызов).
Вы также можете обработать ответ, прежде чем передать его обратному вызову:
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
Написание кода с использованием обратных вызовов проще, чем может показаться. В конце концов, JavaScript в браузере сильно зависит от событий (события DOM). Получение ответа Ajax - не что иное, как событие.
Сложности могут возникнуть, когда вам приходится работать со сторонним кодом, но большинство проблем можно решить, просто продумав поток приложения.
ES2015+: обещания с then()
Promise API - это новая функция ECMAScript 6 (ES2015), но он уже имеет хорошую поддержку браузера. Есть также много библиотек, которые реализуют стандартный API Promises и предоставляют дополнительные методы для упрощения использования и композиции асинхронных функций (например, 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.
Каждый Ajax-метод jQuery уже возвращает "отложенный объект" (фактически обещание отложенного объекта), который вы можете просто вернуть из своей функции:
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()
не замораживает код, пока проверяет страницу "/пароль" на вашем сервере - он отправляет запрос на сервер и, пока ждет, немедленно возвращает объект 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
Если вы напрямую используете объект XMLHTTPRequest
, передайте 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 по своей природе всегда асинхронен (еще одна причина, чтобы даже не рассматривать эту опцию).
Если вы не используете 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, я решил предоставить альтернативу для людей, которые этого не делают.
То, с чем вы сталкиваетесь
Это краткое резюме "Объяснение проблемы" из другого ответа, если вы не уверены, прочитав это, прочитайте это.
A в AJAX означает асинхронный. Это означает, что отправка запроса (или, скорее, получение ответа) вынимается из обычного потока выполнения. В вашем примере .send
возвращается немедленно, а следующий оператор return result;
выполняется до того, как функция, которую вы передали в качестве 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" на экране. (Fiddle).
Возможные решения
В основном есть два способа решения этой проблемы:
- Сделайте вызов AJAX синхронным (позвоните ему SJAX).
- Реструктурируйте свой код для правильной работы с обратными вызовами.
1. Синхронный AJAX - не делайте этого!!
Что касается синхронного AJAX, не делайте этого! Ответ Felix вызывает некоторые веские аргументы в пользу того, почему это плохая идея. Подводя итог, он заморозит браузер пользователя, пока сервер не вернет ответ и не создаст очень плохой пользовательский интерфейс. Вот еще одно краткое изложение 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.
Похожие вопросы
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()
}
Как вы видете:
- Это короче, чем все остальные функции, перечисленные.
- Обратный вызов устанавливается напрямую (поэтому никаких лишних ненужных замыканий).
- Он использует новую загрузку (так что вам не нужно проверять состояние готовности &&)
- Есть некоторые другие ситуации, которые я не помню, которые делают 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, иначе вы его потеряли.
Теперь, если вы хотите что-то более сложное, используя post и 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);
Как видите, я не реализовал синхронизацию... это плохо.
Сказав это... почему бы не сделать это простым способом?
Как уже упоминалось в комментарии, использование error && синхронный полностью нарушает смысл ответа. Какой хороший короткий способ правильно использовать 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 для буфера массива blob или чего-то еще...
Даже если вы передадите POSTAPAPAP в качестве метода, он не выдаст ошибку.
Даже если вы передадите 'fdggdgilfdghfldj' в качестве форм-данных, это не выдаст ошибку.
В первом случае ошибка находится внутри displayAjax()
в this.statusText
как Method not Allowed
.
Во втором случае это просто работает. Вы должны проверить на стороне сервера, если вы передали правильные данные поста.
междоменный домен не разрешен, выдает ошибку автоматически.
В ответе об ошибке нет кодов ошибок.
Существует только this.type
который имеет значение error.
Зачем добавлять обработчик ошибок, если вы полностью не можете контролировать ошибки? Большинство ошибок возвращаются внутри этого в функции обратного вызова displayAjax()
.
Итак: нет необходимости в проверке ошибок, если вы можете правильно скопировать и вставить URL. ;)
PS: В качестве первого теста я написал x ('x', displayAjax)..., и он полностью получил ответ...??? Поэтому я проверил папку, в которой находится HTML, и там был файл с именем "x.xml". Так что даже если вы забудете расширение вашего файла, XMLHttpRequest 2 НАЙДЕТ ЕГО. Я смеюсь
Чтение файла синхронно
Не делай этого.
Если вы хотите на время заблокировать браузер, загрузите красивый большой .txt
файл синхронно.
function omg(a, c){ // URL
c = new XMLHttpRequest;
c.open('GET', a, true);
c.send();
return c; // Or c.response
}
Теперь вы можете сделать
var res = omg('thisIsGonnaBlockThePage.txt');
Нет другого способа сделать это не асинхронно. (Да, с циклом setTimeout... но серьезно?)
Другой момент... если вы работаете с API-интерфейсами или просто со своими собственными файлами списков или какими-либо другими функциями, которые вы всегда используете для каждого запроса...
Только если у вас есть страница, где вы всегда загружаете один и тот же XML/JSON или что-то еще, вам нужна только одна функция. В этом случае немного измените функцию Ajax и замените b своей специальной функцией.
Функции выше предназначены для базового использования.
Если вы хотите расширить функцию...
Да, ты можешь.
Я использую множество API, и одной из первых функций, которые я интегрирую в каждую HTML-страницу, является первая функция Ajax в этом ответе, только с GET...
Но вы можете многое сделать с XMLHttpRequest 2:
Я сделал менеджер загрузок (используя диапазоны с обеих сторон с помощью резюме, файлового ридера, файловой системы), различные конвертеры, изменяющие размер изображения, используя холст, заполнял базы данных веб-SQL с помощью base64image и многое другое... Но в этих случаях вы должны создать функцию только для этого цель... иногда вам нужны блоб, буферы массивов, вы можете установить заголовки, переопределить mimetype и многое другое...
Но вопрос здесь в том, как вернуть ответ Ajax... (я добавил простой способ.)
Если вы используете promises, этот ответ для вас.
Это означает, что AngularJS, jQuery (с отсрочкой), собственная замена XHR (выборка), EmberJS, BackboneJS save или любая библиотека node, которая возвращает promises.
Ваш код должен быть чем-то вроде этого:
function foo() {
var data;
// or $.get(...).then, or request(...).then, or query(...).then
fetch("/echo/json").then(function(response){
data = response.json();
});
return data;
}
var result = foo(); // result is always undefined no matter what.
Феликс Клинг отлично справился с написанием ответа для людей, использующих jQuery с обратными вызовами для AJAX. У меня есть ответ для родного XHR. Этот ответ предназначен для общего использования promises либо на интерфейсе, либо на бэкэнд.
Основная проблема
Модель JavaScript concurrency в браузере и на сервере с NodeJS/io.js является асинхронной и реактивной.
Всякий раз, когда вы вызываете метод, который возвращает обещание, обработчики then
всегда выполняются асинхронно, то есть после ниже кода, который не находится в обработчике .then
.
Это означает, что при возврате data
обработчик then
, который вы определили, еще не выполнил. Это, в свою очередь, означает, что возвращаемое вами значение не было установлено на правильное значение во времени.
Вот простая аналогия проблемы:
function getFive(){
var data;
setTimeout(function(){ // set a timer for one second in the future
data = 5; // after a second, do this
}, 1000);
return data;
}
document.body.innerHTML = getFive(); // `undefined` here and not 5
Значение data
равно undefined
, так как часть data = 5
еще не выполнена. Скорее всего, это произойдет через секунду, но к тому времени это не имеет отношения к возвращенному значению.
Поскольку операция еще не состоялась (AJAX, серверный вызов, ввод-вывод, таймер), вы возвращаете значение до того, как запрос получил возможность сообщить вашему коду, что это за значение.
Одним из возможных решений этой проблемы является повторное использование кода, информирование вашей программы о том, что делать, когда расчет завершен. promises активно активировать это, будучи временными (чувствительными к времени) в природе.
Краткое описание promises
A Promise - это ценность с течением времени. promises имеют состояние, они начинаются как ожидающие без значения и могут рассчитывать на:
- выполнено, что означает, что вычисление выполнено успешно.
- отклонено, что означает, что вычисление завершилось неудачно.
Обещание может изменять состояния только один раз, после чего он всегда будет находиться в одном и том же состоянии навсегда. Вы можете привязать обработчики then
к promises, чтобы извлечь их значение и обработать ошибки. then
позволяют цепочки вызовов. promises создаются с использованием API, которые возвращают их. Например, более современная замена AJAX fetch
или jQuery $.get
возвращает promises.
Когда мы называем .then
обещанием и возвращаем что-то от него - мы получаем обещание для обработанного значения. Если мы вернем другое обещание, мы получим удивительные вещи, но позволим удержать наших лошадей.
С promises
Посмотрим, как решить эту проблему с помощью promises. Во-первых, давайте продемонстрируем наше понимание состояний обещаний сверху, используя конструктор Promise для создания функции задержки:
function delay(ms){ // takes amount of milliseconds
// returns a new promise
return new Promise(function(resolve, reject){
setTimeout(function(){ // when the time is up
resolve(); // change the promise to the fulfilled state
}, ms);
});
}
Теперь, после преобразования setTimeout в использование promises, мы можем использовать then
, чтобы он подсчитал:
function delay(ms){ // takes amount of milliseconds
// returns a new promise
return new Promise(function(resolve, reject){
setTimeout(function(){ // when the time is up
resolve(); // change the promise to the fulfilled state
}, ms);
});
}
function getFive(){
// we're RETURNING the promise, remember, a promise is a wrapper over our value
return delay(100).then(function(){ // when the promise is ready
return 5; // return the value 5, promises are all about return values
})
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){
document.body.innerHTML = five;
});
В принципе, вместо возврата значения, которое мы не можем сделать из-за модели concurrency, мы возвращаем оболочку для значения, которое мы можем развернуть с помощью then
. Это как поле, которое можно открыть с помощью then
.
Применение этого
Это то же самое для вашего первоначального вызова API, вы можете:
function foo() {
// RETURN the promise
return fetch("/echo/json").then(function(response){
return response.json(); // process it inside the `then`
});
}
foo().then(function(response){
// access the value inside the `then`
})
Итак, это работает так же хорошо. Мы узнали, что мы не можем возвращать значения из уже асинхронных вызовов, но мы можем использовать promises и связывать их для выполнения обработки. Теперь мы знаем, как вернуть ответ от асинхронного вызова.
ES2015 (ES6)
ES6 представляет генераторы, которые являются функциями, которые могут возвращаться посередине, а затем возобновлять точку, в которой они были. Это обычно полезно для последовательностей, например:
function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
yield 1;
yield 2;
while(true) yield 3;
}
Является функцией, которая возвращает итератор по последовательности 1,2,3,3,3,3,....
, которая может быть итерирована. Хотя это интересно само по себе и открывает место для большой возможности, есть один интересный случай.
Если последовательность, которую мы производим, представляет собой последовательность действий, а не числа, мы можем приостановить эту функцию всякий раз, когда действие дается и дожидается до того, как мы возобновим работу. Поэтому вместо последовательности чисел нам нужна последовательность будущих значений - то есть: promises.
Этот несколько сложный, но очень мощный трюк позволяет нам писать асинхронный код синхронно. Есть несколько "бегунов", которые делают это за вас, написав один из них - несколько коротких строк кода, но выходит за рамки этого ответа. Я буду использовать Bluebird Promise.coroutine
здесь, но есть другие обертки, такие как co
или Q.async
.
var foo = coroutine(function*(){
var data = yield fetch("/echo/json"); // notice the yield
// code here only executes _after_ the request is done
return data.json(); // data is defined
});
Этот метод возвращает само обещание, которое мы можем использовать из других сопрограмм. Например:
var main = coroutine(function*(){
var bar = yield foo(); // wait our earlier coroutine, it returns a promise
// server call done here, code below executes when done
var baz = yield fetch("/api/users/"+bar.userid); // depends on foo result
console.log(baz); // runs after both requests done
});
main();
ES2016 (ES7)
В ES7 это стандартизировано, сейчас есть несколько предложений, но во всех них вы можете await
обещать. Это просто "сахара" (более сильный синтаксис) для предложения ES6 выше, добавив ключевые слова async
и await
. Вышеприведенный пример:
async function foo(){
var data = await fetch("/echo/json"); // notice the await
// code here only executes _after_ the request is done
return data.json(); // data is defined
}
Он по-прежнему возвращает обещание точно так же:)
Вы неправильно используете Ajax. Идея состоит в том, чтобы не возвращать что-либо, а вместо этого передавать данные на вызов, называемый функцией обратного вызова, которая обрабатывает данные.
То есть:
function handleData( responseData ) {
// Do what you want with the data
console.log(responseData);
}
$.ajax({
url: "hi.php",
...
success: function ( data, status, XHR ) {
handleData(data);
}
});
Возвращение чего-либо в обработчике отправки ничего не сделает. Вы должны либо передать данные, либо делать то, что хотите, непосредственно внутри функции успеха.
Самое простое решение - создать функцию JavaScript и вызвать его для обратного вызова Ajax success
.
function callServerAsync(){
$.ajax({
url: '...',
success: function(response) {
successCallback(response);
}
});
}
function successCallback(responseObj){
// Do something like read the response and show data
alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}
function foo(callback) {
$.ajax({
url: '...',
success: function(response) {
return callback(null, response);
}
});
}
var result = foo(function(err, result){
if (!err)
console.log(result);
});
Я отвечу ужасным, нарисованным рукой комиком. Второе изображение является причиной того, что result
undefined
в вашем примере кода.
Angular1
Для людей, которые используют AngularJS, можно справиться с этой ситуацией, используя Promises
.
Здесь говорится:
Promises может использоваться для отключения асинхронных функций и позволяет объединять несколько функций вместе.
Здесь вы можете найти приятное объяснение здесь.
Пример, найденный в docs, указанный ниже.
promiseB = promiseA.then(
function onSuccess(result) {
return result + 1;
}
,function onError(err) {
//Handle error
}
);
// promiseB will be resolved immediately after promiseA is resolved
// and its value will be the result of promiseA incremented by 1.
Angular2 и позже
В Angular2
просмотрите следующий пример, но его рекомендуется использовать Observables
с Angular2
.
search(term: string) {
return this.http
.get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
.map((response) => response.json())
.toPromise();
}
Вы можете использовать это таким образом,
search() {
this.searchService.search(this.searchField.value)
.then((result) => {
this.result = result.artists.items;
})
.catch((error) => console.error(error));
}
Смотрите оригинал здесь. Но Typescript не поддерживает native es6 Promises, если вы хотите его использовать, для этого вам может понадобиться плагин.
Кроме того, здесь описывается promises spec.
Большинство ответов здесь дают полезные советы, когда у вас есть одна асинхронная операция, но иногда это возникает, когда вам нужно выполнить асинхронную операцию для каждой записи в массиве или другой структуре, подобной списку. Соблазн сделать это:
// WRONG
var results = [];
theArray.forEach(function(entry) {
doSomethingAsync(entry, function(result) {
results.push(result);
});
});
console.log(results); // E.g., using them, returning them, etc.
Пример:
// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
doSomethingAsync(entry, function(result) {
results.push(result);
});
});
console.log("Results:", results); // E.g., using them, returning them, etc.
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
Причина, по которой это не работает, заключается в том, что обратные вызовы от doSomethingAsync
еще не выполнялись к тому времени, когда вы пытаетесь использовать результаты.
Таким образом, если у вас есть массив (или какой-то список) и вы хотите выполнять асинхронные операции для каждой записи, у вас есть два варианта: выполнять операции параллельно (с наложением) или последовательно (одна за другой в последовательности).
Параллельно
Вы можете запустить их все и отследить, сколько обратных вызовов вы ожидаете, а затем использовать результаты, когда вы получили столько обратных вызовов:
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
console.log("Results:", results); // E.g., using the results
}
});
});
Пример:
var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
console.log("Results:", results); // E.g., using the results
}
});
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
(Мы могли бы покончить с expecting
и просто использовать results.length === theArray.length
, но это оставляет нас открытым для вероятности того, что theArray
будет изменен, пока вызовы theArray
...)
Обратите внимание на то, как мы используем index
от forEach
сохранить результат в results
в том же положении, что и запись он относится к, даже если результаты прибывают из строя (так как асинхронные вызовы не обязательно выполнить в том порядке, в котором они были начаты).
Но что, если вам нужно вернуть эти результаты из функции? Как указали другие ответы, вы не можете; вам нужно, чтобы ваша функция принимала и вызывала обратный вызов (или возвращала обещание). Вот версия обратного вызова:
function doSomethingWith(theArray, callback) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
callback(results);
}
});
});
}
doSomethingWith(theArray, function(results) {
console.log("Results:", results);
});
Пример:
function doSomethingWith(theArray, callback) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
callback(results);
}
});
});
}
doSomethingWith([1, 2, 3], function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
Или здесь версия, возвращающая Promise
:
function doSomethingWith(theArray) {
return new Promise(function(resolve) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
resolve(results);
}
});
});
});
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
Конечно, если бы doSomethingAsync
передавал нам ошибки, мы использовали бы reject
чтобы отклонить обещание, когда мы получили ошибку.)
Пример:
function doSomethingWith(theArray) {
return new Promise(function(resolve) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
resolve(results);
}
});
});
});
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
(Или вы можете создать оболочку для doSomethingAsync
которая возвращает обещание, а затем выполнить следующее...)
Если doSomethingAsync
дает вам Promise, вы можете использовать Promise.all
:
function doSomethingWith(theArray) {
return Promise.all(theArray.map(function(entry) {
return doSomethingAsync(entry);
}));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
Если вы знаете, что doSomethingAsync
будет игнорировать второй и третий аргументы, вы можете просто передать его непосредственно на map
(map
вызывает свой обратный вызов с тремя аргументами, но большинство людей используют только первый большую часть времени):
function doSomethingWith(theArray) {
return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
Пример:
function doSomethingWith(theArray) {
return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
Обратите внимание, что Promise.all
разрешает свое обещание с массивом результатов всех обещаний, которые вы даете ему, когда они все разрешены, или отклоняет свое обещание, когда первое из обещаний, которые вы даете, отклоняет его.
Серии
Предположим, вы не хотите, чтобы операции были параллельными? Если вы хотите запускать их один за другим, вам нужно дождаться завершения каждой операции, прежде чем начинать следующую. Вот пример функции, которая делает это и вызывает обратный вызов с результатом:
function doSomethingWith(theArray, callback) {
var results = [];
doOne(0);
function doOne(index) {
if (index < theArray.length) {
doSomethingAsync(theArray[index], function(result) {
results.push(result);
doOne(index + 1);
});
} else {
// Done!
callback(results);
}
}
}
doSomethingWith(theArray, function(results) {
console.log("Results:", results);
});
(Поскольку мы выполняем эту работу последовательно, мы можем просто использовать results.push(result)
поскольку мы знаем, что не получим результаты не по порядку. Выше мы могли использовать results[index] = result;
но в некоторых из следующих примеров у нас нет индекса для использования.)
Пример:
function doSomethingWith(theArray, callback) {
var results = [];
doOne(0);
function doOne(index) {
if (index < theArray.length) {
doSomethingAsync(theArray[index], function(result) {
results.push(result);
doOne(index + 1);
});
} else {
// Done!
callback(results);
}
}
}
doSomethingWith([1, 2, 3], function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
(Или, опять же, создайте оболочку для doSomethingAsync
которая даст вам обещание и выполните следующие действия...)
Если doSomethingAsync
дает вам Обещание, если вы можете использовать синтаксис ES2017+ (возможно, с помощью транспилятора, такого как Babel), вы можете использовать async
функцию с for-of
и await
:
async function doSomethingWith(theArray) {
const results = [];
for (const entry of theArray) {
results.push(await doSomethingAsync(entry));
}
return results;
}
doSomethingWith(theArray).then(results => {
console.log("Results:", results);
});
Пример:
async function doSomethingWith(theArray) {
const results = [];
for (const entry of theArray) {
results.push(await doSomethingAsync(entry));
}
return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
Если вы не можете использовать синтаксис ES2017+ (пока), вы можете использовать вариант шаблона "Обещание уменьшения" (это более сложный вариант, чем обычное уменьшение Обещания, потому что мы не передаем результат из одного в другое, но вместо этого собираем их результаты в массив):
function doSomethingWith(theArray) {
return theArray.reduce(function(p, entry) {
return p.then(function(results) {
return doSomethingAsync(entry).then(function(result) {
results.push(result);
return results;
});
});
}, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
Пример:
function doSomethingWith(theArray) {
return theArray.reduce(function(p, entry) {
return p.then(function(results) {
return doSomethingAsync(entry).then(function(result) {
results.push(result);
return results;
});
});
}, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
... что менее громоздко с функциями стрелок ES2015+:
function doSomethingWith(theArray) {
return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
results.push(result);
return results;
})), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
console.log("Results:", results);
});
Пример:
function doSomethingWith(theArray) {
return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
results.push(result);
return results;
})), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
Посмотрите на этот пример:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,$http) {
var getJoke = function(){
return $http.get('http://api.icndb.com/jokes/random').then(function(res){
return res.data.value;
});
}
getJoke().then(function(res) {
console.log(res.joke);
});
});
Как видите, getJoke
возвращает разрешенное обещание (оно разрешается при возврате res.data.value
). Поэтому вы ждете, пока запрос $ http.get не будет выполнен, а затем будет выполнен console.log(res.joke) (как обычный асинхронный поток).
Это плнкр:
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/
ES6 способ (асинхронно - жду)
(function(){
async function getJoke(){
let response = await fetch('http://api.icndb.com/jokes/random');
let data = await response.json();
return data.value;
}
getJoke().then((joke) => {
console.log(joke);
});
})();
Это одно из мест, где два способа связывания данных или концепция хранения, которые используются во многих новых JavaScript-фреймворках, отлично подойдут для вас...
Поэтому, если вы используете Angular, React или любые другие фреймворки, в которых используется двухстороннее связывание данных или концепция хранения, эта проблема просто решается для вас, поэтому в словом, ваш результат - undefined
на первом этапе, поэтому у вас есть result = undefined
до того, как вы получите данные, а затем, как только вы получите результат, он будет обновлен и будет присвоен новому значению, которое отклик вашего Аякс звонит...
Но как вы можете сделать это на чистом javascript или jQuery, например, как вы задали в этом вопросе?
Вы можете использовать обратный вызов, обещание и недавно наблюдаемую, чтобы обработать его для вас, например, в обещаниях у нас есть какая-то функция, такая как success()
или then()
которая будет выполнена, когда ваши данные будут готовы для вас, то же самое с обратным вызовом или функцией подписки в наблюдаемой.
Например, в вашем случае, который вы используете jQuery, вы можете сделать что-то вроде этого:
$(document).ready(function(){
function foo() {
$.ajax({url: "api/data", success: function(data){
fooDone(data); //after we have data, we pass it to fooDone
}});
};
function fooDone(data) {
console.log(data); //fooDone has the data and console.log it
};
foo(); //call happens here
});
Для получения дополнительной информации изучите обещания и наблюдаемые, которые являются более новыми способами сделать это асинхронным.
Другой подход к возврату значения из асинхронной функции - передать объект, который сохранит результат от асинхронной функции.
Вот пример того же:
var async = require("async");
// This wires up result back to the caller
var result = {};
var asyncTasks = [];
asyncTasks.push(function(_callback){
// some asynchronous operation
$.ajax({
url: '...',
success: function(response) {
result.response = response;
_callback();
}
});
});
async.parallel(asyncTasks, function(){
// result is available after performing asynchronous operation
console.log(result)
console.log('Done');
});
Я использую объект result
для хранения значения во время асинхронной операции. Это позволяет получить результат даже после асинхронного задания.
Я использую этот подход много. Мне было бы интересно узнать, насколько хорошо этот подход работает, когда задействован результат обратно через последовательные модули.
В то время как promises и обратные вызовы работают нормально во многих ситуациях, боль в задней части выражает что-то вроде:
if (!name) {
name = async1();
}
async2(name);
В итоге вы пройдете async1
; проверьте, есть ли name
undefined или нет, и соответственно вызовите обратный вызов.
async1(name, callback) {
if (name)
callback(name)
else {
doSomething(callback)
}
}
async1(name, async2)
Хотя в небольших примерах это нормально, это вызывает раздражение, когда у вас много подобных случаев и обработка ошибок.
Fibers
помогает в решении проблемы.
var Fiber = require('fibers')
function async1(container) {
var current = Fiber.current
var result
doSomething(function(name) {
result = name
fiber.run()
})
Fiber.yield()
return result
}
Fiber(function() {
var name
if (!name) {
name = async1()
}
async2(name)
// Make any number of async calls from here
}
Вы можете проверить проект здесь.
Короткий ответ: вам нужно выполнить обратный вызов следующим образом:
function callback(response) {
// Here you can do what ever you want with the response object.
console.log(response);
}
$.ajax({
url: "...",
success: callback
});
В следующем примере, который я написал, показано, как
- Обрабатывать асинхронные HTTP-вызовы;
- Дождаться ответа от каждого вызова API;
- Используйте шаблон Promise;
- Используйте шаблон Promise.all для объединения нескольких HTTP-вызовов;
Этот рабочий пример самодостаточен. Он определит простой объект запроса, который использует объект окна XMLHttpRequest
для выполнения вызовов. Он определит простую функцию, которая будет ждать выполнения множества обещаний.
Контекст. Пример запрашивает конечную точку Spotify Web API для поиска объектов playlist
для заданного набора строк запроса:
[
"search?type=playlist&q=%22doom%20metal%22",
"search?type=playlist&q=Adele"
]
Для каждого элемента новое Promise будет запускать блок - ExecutionBlock
, анализировать результат, планировать новый набор обещаний на основе массива результатов, который представляет собой список user
объектов Spotify, и выполнять новый HTTP-вызов внутри ExecutionProfileBlock
асинхронно.
Затем вы можете увидеть вложенную структуру Promise, которая позволяет создавать множественные и полностью асинхронные вложенные HTTP-вызовы и объединять результаты каждого подмножества вызовов через Promise.all
.
ПРИМЕЧАНИЕ. Для последних API search
Spotify потребуется указать токен доступа в заголовках запроса:
-H "Authorization: Bearer {your access token}"
Итак, чтобы запустить следующий пример, вам нужно поместить свой токен доступа в заголовки запроса:
var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
log: function(s) {
document.getElementById("console").innerHTML += s + "<br/>"
}
}
// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
call: function(what, response) {
var request;
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
request = new XMLHttpRequest();
} else if (window.ActiveXObject) { // Internet Explorer
try {
request = new ActiveXObject('Msxml2.XMLHTTP');
}
catch (e) {
try {
request = new ActiveXObject('Microsoft.XMLHTTP');
} catch (e) {}
}
}
// State changes
request.onreadystatechange = function() {
if (request.readyState === 4) { // Done
if (request.status === 200) { // Complete
response(request.responseText)
}
else
response();
}
}
request.open('GET', what, true);
request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
request.send(null);
}
}
//PromiseAll
var promiseAll = function(items, block, done, fail) {
var self = this;
var promises = [],
index = 0;
items.forEach(function(item) {
promises.push(function(item, i) {
return new Promise(function(resolve, reject) {
if (block) {
block.apply(this, [item, index, resolve, reject]);
}
});
}(item, ++index))
});
Promise.all(promises).then(function AcceptHandler(results) {
if (done) done(results);
}, function ErrorHandler(error) {
if (fail) fail(error);
});
}; //promiseAll
// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
var url = "https://api.spotify.com/v1/"
url += item;
console.log( url )
SimpleRequest.call(url, function(result) {
if (result) {
var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
return item.owner.href;
})
resolve(profileUrls);
}
else {
reject(new Error("call error"));
}
})
}
arr = [
"search?type=playlist&q=%22doom%20metal%22",
"search?type=playlist&q=Adele"
]
promiseAll(arr, function(item, index, resolve, reject) {
console.log("Making request [" + index + "]")
ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results
console.log("All profiles received " + results.length);
//console.log(JSON.stringify(results[0], null, 2));
///// promiseall again
var ExecutionProfileBlock = function(item, index, resolve, reject) {
SimpleRequest.call(item, function(result) {
if (result) {
var obj = JSON.parse(result);
resolve({
name: obj.display_name,
followers: obj.followers.total,
url: obj.href
});
} //result
})
} //ExecutionProfileBlock
promiseAll(results[0], function(item, index, resolve, reject) {
//console.log("Making request [" + index + "] " + item)
ExecutionProfileBlock(item, index, resolve, reject);
}, function(results) { // aggregated results
console.log("All response received " + results.length);
console.log(JSON.stringify(results, null, 2));
}
, function(error) { // Error
console.log(error);
})
/////
},
function(error) { // Error
console.log(error);
});
<div id="console" />
Я подробно обсудил это решение здесь.
2017 ответ: теперь вы можете делать то, что хотите, в каждом текущем браузере и узле
Это довольно просто:
- Возвращение обещания
- Используйте "ожидание", которое скажет JavaScript, ожидая, что обещание будет разрешено в значение (например, ответ HTTP)
- Добавьте ключевое слово "async" в родительскую функцию
Вот рабочая версия вашего кода:
(async function(){
var response = await superagent.get('...')
console.log(response)
})()
Вы можете использовать эту пользовательскую библиотеку (написанную с помощью Promise) для выполнения удаленного вызова.
function $http(apiConfig) {
return new Promise(function (resolve, reject) {
var client = new XMLHttpRequest();
client.open(apiConfig.method, apiConfig.url);
client.send();
client.onload = function () {
if (this.status >= 200 && this.status < 300) {
// Performs the function "resolve" when this.status is equal to 2xx.
// Your logic here.
resolve(this.response);
}
else {
// Performs the function "reject" when this.status is different than 2xx.
reject(this.statusText);
}
};
client.onerror = function () {
reject(this.statusText);
};
});
}
Простой пример использования:
$http({
method: 'get',
url: 'google.com'
}).then(function(response) {
console.log(response);
}, function(error) {
console.log(error)
});
Другое решение состоит в том, чтобы выполнить код через последовательного исполнителя nsynjs.
Если основная функция обещана
nsynjs будет последовательно оценивать все обещания и помещать результат обещания в свойство data
:
function synchronousCode() {
var getURL = function(url) {
return window.fetch(url).data.text().data;
};
var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
console.log('received bytes:',getURL(url).length);
};
nsynjs.run(synchronousCode,{},function(){
console.log('synchronousCode done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
Если основная функция не обещана
Шаг 1. Оберните функцию с обратным вызовом в оболочку с поддержкой nsynjs (если у нее обещанная версия, вы можете пропустить этот шаг):
var ajaxGet = function (ctx,url) {
var res = {};
var ex;
$.ajax(url)
.done(function (data) {
res.data = data;
})
.fail(function(e) {
ex = e;
})
.always(function() {
ctx.resume(ex);
});
return res;
};
ajaxGet.nsynjsHasCallback = true;
Шаг 2. Ввести синхронную логику в функцию:
function process() {
console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}
Шаг 3. Запустите функцию синхронно через nsynjs:
nsynjs.run(process,this,function () {
console.log("synchronous function finished");
});
Nsynjs будет оценивать все операторы и выражения шаг за шагом, приостанавливая выполнение в случае, если результат какой-то медленной функции не готов.
Дополнительные примеры здесь: https://github.com/amaksr/nsynjs/tree/master/examples
Js - однопоточная.
Браузер можно разделить на три части:
1) Цикл событий
2) Веб-API
3) Очередь событий
Event Loop запускается вечно, т.е. представляет собой бесконечный цикл. Queue - это когда вся ваша функция нажата на какое-либо событие (пример: нажмите), это один за другим выполняется из очереди и помещается в цикл событий, которые выполняют эту функцию и подготавливает его самостоятельно для следующего после первого запуска. Это означает, что выполнение одной функции не запускается до тех пор, пока функция перед ней в очереди не будет выполнена в цикле событий.
Теперь давайте подумаем, что мы поставили две функции в очереди: для получения данных с сервера, а другой использует эти данные. Сначала мы нажали функцию serverRequest() в очереди, а затем применили функцию Data(). Функция serverRequest входит в цикл событий и делает вызов серверу, поскольку мы никогда не знаем, сколько времени потребуется для получения данных с сервера поэтому ожидается, что этот процесс займет много времени, и поэтому мы заняли наш цикл событий, таким образом, висящий на нашей странице, что, когда веб-API входит в роль, он принимает эту функцию из цикла событий и имеет дело с сервером, создающим цикл событий, чтобы мы могли выполнять следующую функцию из queue. Следующая функция в очереди - useData(), которая идет в цикле, но из-за отсутствия доступных данных это идет впустую, а выполнение следующей функции продолжается до конца очереди. (Это называется Async-вызовом, то есть мы можем сделать что-то еще, пока мы получить данные)
Предположим, что наша функция serverRequest() имела оператор возврата в коде, когда мы возвращаем данные с сервера. Web API будет выталкивать его в очередь в конце очереди. Поскольку он оказывается в очереди в очереди, мы не можем использовать его данные, поскольку в нашей очереди нет функции, чтобы использовать эти данные. Таким образом, невозможно вернуть что-то из Async Call.
Таким образом, решение этого - обратный вызов или обещание.
A Изображение из одного из ответов здесь, правильно объясняет использование обратного вызова... Мы предоставляем нашу функцию (функцию, использующую данные, возвращенные с сервера), чтобы вызвать вызывающий сервер.
function doAjax(callbackFunc, method, url) {
var xmlHttpReq = new XMLHttpRequest();
xmlHttpReq.open(method, url);
xmlHttpReq.onreadystatechange = function() {
if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
callbackFunc(xmlHttpReq.responseText);
}
}
xmlHttpReq.send(null);
}
В моем коде он называется
function loadMyJson(categoryValue){
if(categoryValue==="veg")
doAjax(print,"GET","http://localhost:3004/vegetables");
else if(categoryValue==="fruits")
doAjax(print,"GET","http://localhost:3004/fruits");
else
console.log("Data not found");
}
Прочитайте здесь новые методы в ECMA (2016/17) для создания асинхронного вызова (@Felix Kling Answer on Top) fooobar.com/questions/183/...
Это очень распространенная проблема, с которой мы сталкиваемся, борясь с "загадками" JavaScript. Позвольте мне попытаться раскрыть эту тайну сегодня.
Начнем с простой функции JavaScript:
function foo(){
// do something
return 'wohoo';
}
let bar = foo(); // bar is 'wohoo' here
Это простой синхронный вызов функции (где каждая строка кода "заканчивается своим заданием" перед следующей последовательной), и результат такой же, как и ожидалось.
Теперь давайте добавим немного поворота, внося небольшую задержку в нашу функцию, чтобы все строки кода не были "закончены" в последовательности. Таким образом, он будет эмулировать асинхронное поведение функции:
function foo(){
setTimeout( ()=>{
return 'wohoo';
}, 1000 )
}
let bar = foo() // bar is undefined here
Итак, эта задержка просто нарушила функциональность, которую мы ожидали! Но что именно произошло? Ну, это на самом деле довольно логично, если вы посмотрите на код. функция foo()
после выполнения ничего не возвращает (таким образом, возвращаемое значение - undefined
), но она запускает таймер, который выполняет функцию через 1 с, чтобы вернуть 'wohoo'. Но, как вы можете видеть, значение, присвоенное bar, является немедленно возвращаемым материалом из foo(), а не чем-либо другим, что будет позже.
Итак, как нам решить эту проблему?
Позвольте спросить нашу функцию для ОБЕЩАНИЯ. Обещание действительно о том, что оно означает: это означает, что функция гарантирует, что вы обеспечите любой вывод, который она получит в будущем. так что давайте рассмотрим это в действии для нашей маленькой проблемы выше:
function foo(){
return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
setTimeout ( function(){
// promise is RESOLVED , when execution reaches this line of code
resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
}, 1000 )
})
}
let bar ;
foo().then( res => {
bar = res;
console.log(bar) // will print 'wohoo'
});
Таким образом, краткое изложение - для решения асинхронных функций, таких как вызовы на основе ajax и т.д., вы можете использовать обещание resolve
для значения (которое вы намереваетесь вернуть). Таким образом, короче говоря, вы разрешаете значение вместо возврата в асинхронных функциях.
ОБНОВЛЕНИЕ (обещания с асинхронным ожиданием)
Помимо использования then/catch
для работы с обещаниями, существует еще один подход. Идея состоит в том, чтобы распознать асинхронную функцию и затем дождаться разрешения обещаний, прежде чем перейти к следующей строке кода. Это все еще только promises
под капотом, но с другим синтаксическим подходом. Чтобы прояснить ситуацию, вы можете найти сравнение ниже:
затем/перехват версии:
function saveUsers(){
getUsers()
.then(users => {
saveSomewhere(users);
})
.catch(err => {
throw err;
})
}
асинхронная/ожидающая версия:
async function saveUsers(){
try{
let users = await getUsers()
saveSomewhere(users);
}
catch(err){
throw err;
}
}
ECMAScript 6 имеет "генераторы", которые позволяют легко программировать в асинхронном стиле.
function* myGenerator() {
const callback = yield;
let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
console.log("response is:", response);
// examples of other things you can do
yield setTimeout(callback, 1000);
console.log("it delayed for 1000ms");
while (response.statusText === "error") {
[response] = yield* anotherGenerator();
}
}
Для запуска вышеуказанного кода вы выполните следующее:
const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function
Если вам нужно настроить таргетинг на браузеры, которые не поддерживают ES6, вы можете запустить код через Babel или make-compiler для генерации ECMAScript 5.
Обратные вызовы ...args
завернуты в массив и деструктурированы, когда вы их читаете, чтобы шаблон мог справиться с обратными вызовами, имеющими несколько аргументов. Например, с узлом fs:
const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
Ниже приведены некоторые подходы к работе с асинхронными запросами:
- Объект Promise Browser
- Q - библиотека обещаний для JavaScript
- A + Promises.js
- jQuery отложен
- API XMLHttpRequest
- Использование концепции обратного вызова - как реализация в первом ответе
Пример: jQuery отложенная реализация для работы с несколькими запросами
var App = App || {};
App = {
getDataFromServer: function(){
var self = this,
deferred = $.Deferred(),
requests = [];
requests.push($.getJSON('request/ajax/url/1'));
requests.push($.getJSON('request/ajax/url/2'));
$.when.apply(jQuery, requests).done(function(xhrResponse) {
return deferred.resolve(xhrResponse.result);
});
return deferred;
},
init: function(){
this.getDataFromServer().done(_.bind(function(resp1, resp2) {
// Do the operations which you wanted to do when you
// get a response from Ajax, for example, log response.
}, this));
}
};
App.init();
Короткий ответ. Ваш метод foo()
возвращается немедленно, а вызов $ajax()
выполняется асинхронно после возвращения функции. Проблема заключается в том, как и где хранить результаты, полученные при вызове async, после его возврата.
В этой цепочке даны несколько решений. Возможно, самый простой способ - передать объект методу foo()
и сохранить результаты в члене этого объекта после завершения асинхронного вызова.
function foo(result) {
$.ajax({
url: '...',
success: function(response) {
result.response = response; // Store the async result
}
});
}
var result = { response: null }; // Object to hold the async result
foo(result); // Returns before the async completes
Обратите внимание, что вызов foo()
по-прежнему не возвращает ничего полезного. Однако результат асинхронного вызова теперь будет сохранен в result.response
.
Используйте функцию callback()
внутри успеха foo()
.
Попробуйте таким образом. Это просто и легко понять.
var lat = "";
var lon = "";
function callback(data) {
lat = data.lat;
lon = data.lon;
}
function getLoc() {
var url = "http://ip-api.com/json"
$.getJSON(url, function(data) {
callback(data);
});
}
getLoc();
Мы попадаем во вселенную, которая, кажется, движется в измерении, которое мы называем "время". Мы не очень понимаем, что такое время, но мы разработали абстракции и словарный запас, которые позволяют нам рассуждать и говорить об этом: "прошлое", "настоящее", "будущее", "до", "после".
Компьютерные системы, которые мы создаем - все больше и больше - имеют время как важное измерение. Определенные вещи созданы, чтобы произойти в будущем. Затем должны произойти другие вещи после того, как эти первые вещи в конечном итоге произойдут Это основное понятие называется "асинхронность". В нашем мире, который становится все более сетевым, наиболее распространенный случай асинхронности - ожидание того, что удаленная система ответит на какой-либо запрос.
Рассмотрим пример. Вы звоните молочнику и заказываете молоко. Когда это приходит, вы хотите положить его в свой кофе. Вы не можете положить молоко в свой кофе прямо сейчас, потому что его еще нет. Вы должны ждать, пока оно придет, прежде чем положить его в свой кофе. Другими словами, следующее не будет работать:
var milk = order_milk();
put_in_coffee(milk);
Потому что у JS нет возможности узнать, что ему нужно дождаться order_milk
прежде чем он выполнит put_in_coffee
. Другими словами, он не знает, что order_milk
является асинхронным --is, что не приведет к появлению молока до некоторого будущего времени. JS и другие декларативные языки выполняют один оператор за другим без ожидания.
Классический подход JS к этой проблеме, использующий преимущество того факта, что JS поддерживает функции как объекты первого класса, которые могут быть переданы, состоит в том, чтобы передать функцию в качестве параметра асинхронному запросу, который он затем вызовет после завершения. его задача когда-нибудь в будущем. Это подход "обратного вызова". Это выглядит так:
order_milk(put_in_coffee);
order_milk
, заказывает молоко, затем, когда и только когда оно прибывает, оно вызывает put_in_coffee
.
Проблема с этим подходом обратного вызова состоит в том, что он загрязняет нормальную семантику функции, сообщающей о своем результате с return
; вместо этого функции не должны сообщать о своих результатах, вызывая функцию обратного вызова, заданную в качестве параметра. Кроме того, этот подход может быстро стать громоздким при работе с более длинными последовательностями событий. Например, допустим, что я хочу подождать, пока молоко будет добавлено в кофе, а затем и только тогда выполнить третий шаг, а именно выпить кофе. В итоге мне нужно написать что-то вроде этого:
order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }
где я передаю put_in_coffee
и молоко, которое нужно вставить в него, и действие (drink_coffee
), которое нужно выполнить после того, как молоко было введено. Такой код становится трудным для написания, чтения и отладки.
В этом случае мы могли бы переписать код в вопросе как:
var answer;
$.ajax('/foo.json') . done(function(response) {
callback(response.data);
});
function callback(data) {
console.log(data);
}
Введите обещания
Это послужило мотивацией для понятия "обещание", которое представляет собой особый тип значения, представляющий будущий или какой-то асинхронный результат. Он может представлять то, что уже произошло, или произойдет в будущем, или может никогда не произойти вообще. У обещаний есть единственный метод, названный then
, которому вы передаете действие, которое будет выполнено, когда результат, который представляет обещание, был реализован.
В случае с нашим молоком и кофе мы проектируем order_milk
чтобы он возвращал обещание на прибытие молока, а затем определяем put_in_coffee
в качестве действия then
, как put_in_coffee
ниже:
order_milk() . then(put_in_coffee)
Одним из преимуществ этого является то, что мы можем связать их вместе, чтобы создать последовательности будущих вхождений ("цепочка"):
order_milk() . then(put_in_coffee) . then(drink_coffee)
Пусть применят обещания к вашей конкретной проблеме. Мы обернем нашу логику запроса внутри функции, которая возвращает обещание:
function get_data() {
return $.ajax('/foo.json');
}
На самом деле все, что мы сделали, это добавили return
к вызову $.ajax
. Это работает, потому что jQuery $.ajax
уже возвращает что-то вроде обещания. (На практике, не вдаваясь в подробности, мы бы предпочли обернуть этот вызов так, чтобы он возвращал реальное обещание, или использовать какую-то альтернативу $.ajax
которая делает это.) Теперь, если мы хотим загрузить файл и дождаться его чтобы закончить, а затем сделать что-то, мы можем просто сказать,
get_data() . then(do_something)
например,
get_data() .
then(function(data) { console.log(data); });
При использовании обещания, мы в конечном итоге прохожу множество функций в then
, так что часто бывает полезно использовать более компактные функции со стрелками ES6 стиля:
get_data() .
then(data => console.log(data));
async
ключевое слово
Но все же есть что-то смутно неудовлетворенное тем, что нужно писать код одним способом, если он синхронный, и совершенно другим способом, если он асинхронный. Для синхронного мы пишем
a();
b();
но если a
асинхронный, с обещаниями мы должны написать
a() . then(b);
Выше мы говорили: "JS не может знать, что ему нужно дождаться завершения первого вызова, прежде чем он выполнит второй". Разве не было бы хорошо, если бы был какой-то способ сказать JS об этом? Оказывается, есть ключевое слово await
, используемое внутри специального типа функции, называемой "асинхронной" функцией. Эта функция является частью будущей версии ES, но она уже доступна в таких транспортерах, как Babel, с правильными настройками. Это позволяет нам просто написать
async function morning_routine() {
var milk = await order_milk();
var coffee = await put_in_coffee(milk);
await drink(coffee);
}
В вашем случае вы сможете написать что-то вроде
async function foo() {
data = await get_data();
console.log(data);
}
Конечно, существует много подходов, таких как синхронный запрос, обещание, но из моего опыта я думаю, что вы должны использовать подход обратного вызова. Это естественно для асинхронного поведения Javascript. Итак, ваш фрагмент кода можно переписать немного иначе:
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
myCallback(response);
}
});
return result;
}
function myCallback(response) {
// Does something.
}
Вопрос был:
Как вернуть ответ от асинхронного вызова?
который МОЖЕТ интерпретироваться как:
Как сделать асинхронный код синхронным?
Решение будет состоять в том, чтобы избежать обратных вызовов и использовать комбинацию Promises и async/wait.
Я хотел бы привести пример для запроса Ajax.
(Хотя он может быть написан в Javascript, я предпочитаю писать его на Python и компилировать его в Javascript с помощью Transcrypt. Это будет достаточно ясно.)
Позволяет сначала включить использование JQuery, иметь $
доступный как S
:
__pragma__ ('alias', 'S', '$')
Определите функцию, которая возвращает Promise, в этом случае вызов Ajax:
def read(url: str):
deferred = S.Deferred()
S.ajax({'type': "POST", 'url': url, 'data': { },
'success': lambda d: deferred.resolve(d),
'error': lambda e: deferred.reject(e)
})
return deferred.promise()
Используйте асинхронный код, как если бы он был синхронным:
async def readALot():
try:
result1 = await read("url_1")
result2 = await read("url_2")
except Exception:
console.warn("Reading a lot failed")
Используя Promise
Самый совершенный ответ на этот вопрос - использование Promise
.
function ajax(method, url, params) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open(method, url);
xhr.send(params);
});
}
использование
ajax("GET", "/test", "acrive=1").then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
Но ждать...!
Существует проблема с использованием обещаний!
Почему мы должны использовать наши собственные обещания?
Я использовал это решение некоторое время, пока не выяснил, есть ли ошибка в старых браузерах:
Uncaught ReferenceError: Promise is not defined
Поэтому я решил реализовать свой собственный класс Promise для ES3 для компиляторов ниже js, если он не определен. Просто добавьте этот код перед вашим основным кодом, а затем смело используйте Promise!
if(typeof Promise === "undefined"){
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Promise = function () {
function Promise(main) {
var _this = this;
_classCallCheck(this, Promise);
this.value = undefined;
this.callbacks = [];
var resolve = function resolve(resolveValue) {
_this.value = resolveValue;
_this.triggerCallbacks();
};
var reject = function reject(rejectValue) {
_this.value = rejectValue;
_this.triggerCallbacks();
};
main(resolve, reject);
}
Promise.prototype.then = function then(cb) {
var _this2 = this;
var next = new Promise(function (resolve) {
_this2.callbacks.push(function (x) {
return resolve(cb(x));
});
});
return next;
};
Promise.prototype.catch = function catch_(cb) {
var _this2 = this;
var next = new Promise(function (reject) {
_this2.callbacks.push(function (x) {
return reject(cb(x));
});
});
return next;
};
Promise.prototype.triggerCallbacks = function triggerCallbacks() {
var _this3 = this;
this.callbacks.forEach(function (cb) {
cb(_this3.value);
});
};
return Promise;
}();
}
Вместо того, чтобы бросать код на вас, есть два понятия, которые являются ключом к пониманию того, как JS обрабатывает обратные вызовы и асинхронность. (Это вообще слово?)
Модель цикла событий и параллелизма
Вам нужно знать три вещи; Очередь; цикл события и стек
В широких, упрощенных терминах цикл событий похож на менеджера проекта, он постоянно слушает любые функции, которые хотят запускать и обмениваться между очередью и стеком.
while (queue.waitForMessage()) {
queue.processNextMessage();
}
Когда он получает сообщение для запуска чего-то, он добавляет его в очередь. Очередь - это список вещей, которые ждут выполнения (например, ваш запрос AJAX). представьте себе это так:
1. call foo.com/api/bar using foobarFunc
2. Go perform an infinite loop
... and so on
Когда одно из этих сообщений собирается выполнить, оно выталкивает сообщение из очереди и создает стек, стек - все, что нужно выполнить JS для выполнения инструкции в сообщении. Итак, в нашем примере ему сказали позвонить foobarFunc
function foobarFunc (var) {
console.log(anotherFunction(var));
}
Так что все, что foobarFunc должно выполнить (в нашем случае anotherFunction
), будет вдавлено в стек. выполненный, а затем забытый - цикл события затем переместится на следующую вещь в очереди (или прослушивает сообщения)
Главное здесь - порядок исполнения. То есть
КОГДА что-то будет запущено
Когда вы выполняете вызов с использованием AJAX на внешнюю сторону или запускаете любой асинхронный код (например, setTimeout), Javascript зависит от ответа, прежде чем он сможет продолжить.
Большой вопрос, когда он получит ответ? Ответ в том, что мы не знаем, поэтому цикл событий ждет, когда это сообщение скажет: "Эй, забери меня". Если JS просто ждал этого сообщения синхронно, ваше приложение замерзнет, и оно сосать. Поэтому JS продолжает выполнение следующего элемента в очереди, ожидая, пока сообщение не будет добавлено обратно в очередь.
Именно поэтому с асинхронной функциональностью мы используем вещи, называемые обратными вызовами. Это похоже на обещание буквально. Как и я, я обещаю что-то вернуть в какой-то момент. JQuery использует определенные обратные вызовы, называемые deffered.done
deffered.fail
и deffered.always
(среди прочих). Вы можете увидеть их все здесь
Итак, вам нужно передать функцию, которая в какой-то момент должна выполнить с переданными ей данными.
Поскольку обратный вызов не выполняется немедленно, но в более позднее время важно передать ссылку на функцию, которую он не выполнил. так
function foo(bla) {
console.log(bla)
}
поэтому большую часть времени (но не всегда) вы пройдете foo
not foo()
Надеюсь, это будет иметь смысл. Когда вы сталкиваетесь с такими вещами, которые кажутся запутанными, я настоятельно рекомендую полностью прочитать документацию, чтобы хотя бы понять ее. Это сделает вас намного лучше разработчиков.
Используя ES2017, вы должны иметь это как объявление функции
async function foo() {
var response = await $.ajax({url: '...'})
return response;
}
И выполните его так.
(async function() {
try {
var result = await foo()
console.log(result)
} catch (e) {}
})()
Или синтаксис Promise
foo().then(response => {
console.log(response)
}).catch(error => {
console.log(error)
})
- 1
- 2
Посмотрите другие вопросы по меткам javascript jquery ajax asynchronous ecmascript-6 xmlhttprequest ecmascript-2017 event-loop или Задайте вопрос