67

Как вернуть ответ от асинхронного вызова?

17

Как вернуть ответ/результат из функции 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 ответ(ов)

2

Вы неправильно используете Ajax. Суть в том, что он не должен ничего возвращать. Вместо этого вам нужно передать данные функции обратного вызова (callback function), которая будет обрабатывать эти данные.

Вот пример:

function handleData(responseData) {
    // Делайте с данными то, что вам нужно
    console.log(responseData);
}

$.ajax({
    url: "hi.php",
    ...
    success: function(data, status, XHR) {
        handleData(data);
    }
});

Возвращение чего-либо в обработчике отправки не даст никакого результата. Вам нужно либо передать данные в функцию, либо обрабатывать их напрямую внутри функции success.

2

Самое простое решение — создать функцию на 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 также выполняется запрос, но в данном случае используется колбэк для передачи результата или ошибки.

1

Взгляните на этот пример:

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.

1

Это довольно распространённая проблема, с которой мы сталкиваемся, пытаясь разобраться в "тайнах" 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!

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь