Как вернуть ответ от асинхронного вызова?
Как вернуть ответ/результат из функции foo
, которая выполняет асинхронный запрос?
Я пытаюсь вернуть значение из колбэка, а также присвоить результат локальной переменной внутри функции и вернуть её, но ни один из этих способов не работает — они все возвращают undefined
или начальное значение переменной result
.
Пример асинхронной функции, которая принимает колбэк (используя функцию ajax
из jQuery):
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
result = response;
// return response; // <- Я пробовал и это
}
});
return result; // Всегда возвращает `undefined`
}
Пример с использованием Node.js:
function foo() {
var result;
fs.readFile("path/to/file", function(err, data) {
result = data;
// return data; // <- Я пробовал и это
});
return result; // Всегда возвращает `undefined`
}
Пример с использованием блока then
промиса:
function foo() {
var result;
fetch(url).then(function(response) {
result = response;
// return response; // <- Я пробовал и это
});
return result; // Всегда возвращает `undefined`
}
Как решить эту проблему и правильно вернуть результат из асинхронной функции?
4 ответ(ов)
Вы неправильно используете Ajax. Суть в том, что он не должен ничего возвращать. Вместо этого вам нужно передать данные функции обратного вызова (callback function), которая будет обрабатывать эти данные.
Вот пример:
function handleData(responseData) {
// Делайте с данными то, что вам нужно
console.log(responseData);
}
$.ajax({
url: "hi.php",
...
success: function(data, status, XHR) {
handleData(data);
}
});
Возвращение чего-либо в обработчике отправки не даст никакого результата. Вам нужно либо передать данные в функцию, либо обрабатывать их напрямую внутри функции success
.
Самое простое решение — создать функцию на JavaScript и вызывать её в колбэке success
для Ajax-запроса.
function callServerAsync(){
$.ajax({
url: '...',
success: function(response) {
successCallback(response);
}
});
}
function successCallback(responseObj){
// Обработка ответа и отображение данных
alert(JSON.stringify(responseObj)); // Применимо только для JSON ответа
}
function foo(callback) {
$.ajax({
url: '...',
success: function(response) {
return callback(null, response);
}
});
}
var result = foo(function(err, result){
if (!err)
console.log(result);
});
Этот пример показывает, как можно асинхронно получить данные с сервера и обработать их в разных функциях. В функции callServerAsync
выполняется AJAX-запрос, после успешного выполнения которого вызывается successCallback
для обработки ответа. В функции foo
также выполняется запрос, но в данном случае используется колбэк для передачи результата или ошибки.
Взгляните на этот пример:
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)
(что является нормальным асинхронным потоком).
Вот plnkr:
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/
Способ на ES6 (async - await)
(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);
});
})();
В этом примере используется синтаксис async/await
, который позволяет сделать код более читаемым и понятным, избавляя от необходимости использовать цепочку промисов. Функция getJoke
теперь объявлена как async
, и с помощью await
мы ждем завершения получения ответа и его преобразования в JSON.
Это довольно распространённая проблема, с которой мы сталкиваемся, пытаясь разобраться в "тайнах" JavaScript. Давайте попробуем развеять этот миф сегодня.
Начнём с простой функции на JavaScript:
function foo(){
// Выполнить что-то
return 'wohoo';
}
let bar = foo(); // Здесь 'bar' будет 'wohoo'
Это простой синхронный вызов функции (где каждая строка кода "завершает свою работу" перед переходом к следующей в последовательности), и результат соответствует ожидаемому.
Теперь добавим немного изюминки, введя небольшую задержку в нашу функцию, чтобы все строки кода не "завершались" в последовательности. Таким образом, это будет эмулировать асинхронное поведение функции:
function foo(){
setTimeout( () => {
return 'wohoo';
}, 1000);
}
let bar = foo(); // Здесь 'bar' будет undefined
И вот мы столкнулись с проблемой: эта задержка сломала ожидаемую функциональность! Но что же произошло на самом деле? Давайте разберёмся.
Функция foo()
, при выполнении, ничего не возвращает (поэтому возвращаемое значение undefined
), но она запускает таймер, который выполняет функцию через 1 секунду и возвращает 'wohoo'. Как видите, значение, присвоенное переменной bar
, это сразу возвращаемое значение функции foo(), а это ничто, т.е. просто undefined
.
Так как же нам справиться с этой проблемой?
Давайте попросим нашу функцию о промисе. Промис — это именно то, что говорит его название: это означает, что функция гарантирует предоставить вам любое значение, которое она получит в будущем. Давайте посмотрим, как это работает на нашем примере:
function foo(){
return new Promise((resolve, reject) => { // Я хочу, чтобы foo() пообещала мне что-то
setTimeout(() => {
// Промис считается РЕШЁННЫМ, когда выполнение доходит до этой строки кода
resolve('wohoo'); // После 1 секунды РЕШИТЕ промис со значением 'wohoo'
}, 1000);
});
}
let bar;
foo().then(res => {
bar = res;
console.log(bar); // Выведет 'wohoo'
});
Таким образом, резюме: для работы с асинхронными функциями, такими как Ajax-вызовы и т.п., вы можете использовать промис для разрешения значения (которое вы собираетесь вернуть). Короче говоря, вы разрешаете значение вместо того, чтобы возвращать его в асинхронных функциях.
ОБНОВЛЕНИЕ (Промисы с async/await)
Помимо использования then/catch
для работы с промисами, существует ещё один подход. Идея заключается в том, чтобы распознать асинхронную функцию и затем подождать, пока промисы разрешатся, прежде чем переходить к следующей строке кода. Это всё ещё промисы под капотом, но с другим синтаксическим подходом. Чтобы прояснить, ниже приведено сравнение:
Версия then/catch:
function saveUsers(){
getUsers()
.then(users => {
saveSomewhere(users);
})
.catch(err => {
console.error(err);
});
}
Версия async/await:
async function saveUsers(){
try {
let users = await getUsers();
saveSomewhere(users);
} catch(err) {
console.error(err);
}
}
Надеюсь, это поможет вам лучше понять, как работает асинхронный код в JavaScript!
Как загружать файлы асинхронно с помощью jQuery?
Как заставить jQuery выполнять синхронный запрос Ajax вместо асинхронного?
Прокрутка до низа div?
Кэширует ли Safari на iOS 6 результаты $.ajax?
Почему моя переменная не изменяется после модификации внутри функции? - Ссылка на асинхронный код