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

У меня есть функция 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`.

47 ответов

Решение

→ Для более общего объяснения асинхронного поведения на разных примерах см. Почему моя переменная не изменяется после того, как я изменил ее внутри функции? - асинхронная ссылка на код

→ Если вы уже поняли проблему, перейдите к возможным решениям ниже.

Эта проблема

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 функция. Это означает, что на самом верхнем уровне вам все равно придется работать непосредственно с обещанием.

Вы можете прочитать больше о 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;
  }
}

// Async functions always return a promise
getAllBooks()
  .then(function(books) {
    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. Я решил предоставить альтернативу тем, кто этого не делает.

( Обратите внимание, для тех, кто использует новый fetch API, Angular или обещания я добавил еще один ответ ниже)


С чем вы сталкиваетесь

Это краткое изложение "Объяснение проблемы" из другого ответа, если вы не уверены, прочитав это, прочитайте это.

A в AJAX означает асинхронный. Это означает, что отправка запроса (или, вернее, получение ответа) вынимается из обычного потока выполнения. В вашем примере .send немедленно возвращается и следующее утверждение, return result;, выполняется перед функцией, которую вы передали как success Обратный звонок даже был вызван.

Это означает, что когда вы возвращаете, определенный слушатель еще не выполнялся, то есть возвращаемое вами значение не было определено.

Вот простая аналогия

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

(Fiddle)

Значение 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" на экране. (Скрипка).

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

Есть два основных способа решения этой проблемы:

  1. Сделайте вызов AJAX синхронным (давайте назовем его SJAX).
  2. Реструктурируйте ваш код для правильной работы с обратными вызовами.

1. Синхронный AJAX - не делай этого!!

Что касается синхронного AJAX, не делайте этого! Ответ Феликса вызывает некоторые убедительные аргументы о том, почему это плохая идея. Подводя итог, он замораживает браузер пользователя до тех пор, пока сервер не вернет ответ и не создаст очень плохой пользовательский опыт. Вот еще одно краткое изложение MDN о том, почему:

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

Короче говоря, синхронные запросы блокируют выполнение кода...... это может вызвать серьезные проблемы...

Если вам нужно сделать это, вы можете передать флаг: Вот как:

var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
request.send(null);

if (request.status === 200) {// That's 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);

Для получения более подробной информации о том, как осуществляется этот дизайн обратного вызова, проверьте ответ Феликса.

Теперь давайте определим сам 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()
}

Как вы видете:

  1. Это короче, чем все остальные функции, перечисленные.
  2. Обратный вызов устанавливается напрямую (поэтому никаких лишних ненужных замыканий).
  3. Он использует новую загрузку (так что вам не нужно проверять состояние готовности &&)
  4. Есть некоторые другие ситуации, которые я не помню, которые делают 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's 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 который установлен на ошибку.

Зачем добавлять обработчик ошибок, если вы полностью не можете контролировать ошибки? Большинство ошибок возвращаются внутри этого в функции обратного вызова displayAjax(),

Итак: нет необходимости в проверке ошибок, если вы можете правильно скопировать и вставить URL.;)

PS: В качестве первого теста я написал x('x', displayAjax)..., и он полностью получил ответ...??? Поэтому я проверил папку, в которой находится HTML, и там был файл с именем "x.xml". Так что даже если вы забудете расширение вашего файла, XMLHttpRequest 2 НАЙДЕТ ЕГО. Я смеюсь


Чтение файла синхронно

Не делай этого.

Если вы хотите на время заблокировать браузер, загрузите красивый большой текстовый файл синхронно.

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:

Я сделал менеджер загрузок (используя диапазоны с обеих сторон с помощью резюме, файлового ридера, файловой системы), различные конвертеры, изменяющие размер изображения, используя canvas, заполнил базы данных websql base64images и многое другое... Но в этих случаях вы должны создавать функцию только для этой цели... иногда вам нужны блоб, буферы массивов, вы можете установить заголовки, переопределить mimetype и многое другое...

Но вопрос здесь в том, как вернуть ответ Ajax... (я добавил простой способ.)

Если вы используете обещания, этот ответ для вас.

Это означает AngularJS, jQuery (с отсрочкой), замену (извлечение) собственного XHR, сохранение EmberJS, BackboneJS или любую библиотеку узлов, которая возвращает обещания.

Ваш код должен быть примерно таким:

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. Этот ответ предназначен для общего использования обещаний либо в веб-интерфейсе, либо в бэкэнде.


Основная проблема

Модель параллелизма JavaScript в браузере и на сервере с 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, серверный вызов, IO, таймер), вы возвращаете значение до того, как запрос получит возможность сообщить вашему коду, что это за значение.

Одним из возможных решений этой проблемы является повторное активное программирование, сообщающее вашей программе, что делать после завершения расчета. Обещания активно способствуют этому, будучи временными (чувствительными ко времени) по своей природе.

Краткий обзор обещаний

Обещание - это ценность с течением времени. Обещания имеют состояние, они начинаются как ожидающие без значения и могут рассчитывать на:

  • означает, что вычисление завершено успешно.
  • отклонено означает, что вычисление не удалось.

Обещание может изменить состояние только один раз, после чего оно всегда будет оставаться в одном и том же состоянии навсегда. Вы можете прикрепить then обработчики обещаний извлекать их значение и обрабатывать ошибки. then обработчики позволяют связывать вызовы. Обещания создаются с помощью API, которые их возвращают. Например, более современная замена AJAX fetch или JQuery's $.get вернуть обещания.

Когда мы звоним .then на обещание и вернуть что-то из него - мы получаем обещание для обработанного значения. Если мы ответим на другое обещание, мы получим удивительные вещи, но давайте держать наших лошадей.

С обещаниями

Давайте посмотрим, как мы можем решить вышеуказанную проблему с обещаниями. Во-первых, давайте продемонстрируем наше понимание состояний обещаний сверху, используя конструктор 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 для использования обещаний, мы можем использовать 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;
});

По сути, вместо возврата значения, которое мы не можем сделать из-за модели параллелизма - мы возвращаем оболочку для значения, которое мы можем развернуть 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`
})

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

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,.... который может быть повторен. Хотя это само по себе интересно и открывает много возможностей, есть один интересный случай.

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

Этот несколько хитрый, но очень мощный трюк позволяет нам писать асинхронный код синхронно. Есть несколько "бегунов", которые делают это для вас, написание одного - несколько строк кода, но выходит за рамки этого ответа. Я буду использовать Bluebird's 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's 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);
    }
});

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

Я отвечу ужасно выглядящим, нарисованным от руки комиксом. Второе изображение является причиной, почему result является undefined в вашем примере кода.

введите описание изображения здесь

Самое простое решение - создать функцию 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);    
}); 

Angular1

Для людей, которые используют AngularJS, могут справиться с этой ситуацией, используя Promises,

Здесь говорится,

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

Вы также можете найти хорошее объяснение здесь.

Пример найден в документах, упомянутых ниже.

  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 не поддерживает встроенные обещания es6, если вы хотите его использовать, вам может понадобиться плагин для этого.

Дополнительно здесь есть спецификация обещаний здесь.

Большинство ответов здесь дают полезные советы, когда у вас есть одна асинхронная операция, но иногда это происходит, когда вам нужно выполнить асинхронную операцию для каждой записи в массиве или другой структуре, подобной списку. Соблазн сделать это:

// 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 меняется, пока звонки ожидают...)

Обратите внимание, как мы используем 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.all:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry, function(result) {
            results.push(result);
        });
    }));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Пример:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry, function(result) {
            results.push(result);
        });
    }));
}
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 прежде чем вы получите данные, затем, как только вы получите результат, он обновится и будет присвоен новое значение, которое является ответом на ваш вызов Ajax...

Но как вы можете сделать это в чистом 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
});

Для получения дополнительной информации изучите обещания и наблюдаемые, которые являются более новыми способами сделать это асинхронным материалом.

Это очень распространенная проблема, с которой мы сталкиваемся, борясь с "загадками" 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 fetchUsers(){
   let users = [];
   getUsers()
   .then(_users => users = _users)
   .catch(err =>{
      throw err
   })
   return users;
 }

версия async/await:

  async function fetchUsers(){
     try{
        let users = await getUsers()
        return users;
     }
     catch(err){
        throw err;
     }
  }

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

Вот пример того же:

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 объект для хранения значения во время асинхронной операции. Это позволяет результату быть доступным даже после асинхронного задания.

Я часто использую этот подход. Мне было бы интересно узнать, насколько хорошо работает этот подход, когда речь идет о передаче результата через последовательные модули.

Несмотря на то, что обещания и обратные вызовы работают хорошо во многих ситуациях, в тылу трудно выразить что-то вроде:

if (!name) {
  name = async1();
}
async2(name);

Вы бы в конечном итоге пройти async1; проверить, если name не определено или нет, и вызовите обратный вызов соответственно.

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
}

Вы можете оформить заказ здесь.

В следующем примере, который я написал, показано, как

  • Обрабатывать асинхронные HTTP-вызовы;
  • Дождаться ответа от каждого вызова API;
  • Используйте шаблон Promise;
  • Используйте шаблон Promise.all для объединения нескольких HTTP-вызовов;

Этот рабочий пример самодостаточен. Он определит простой объект запроса, который использует окно XMLHttpRequest объект для звонков. Он определит простую функцию, которая будет ждать выполнения множества обещаний.

Контекст. Пример запрашивает конечную точку Spotify Web API для поиска playlist объекты для заданного набора строк запроса:

[
 "search?type=playlist&q=%22doom%20metal%22",
 "search?type=playlist&q=Adele"
]

Для каждого предмета новое Обещание будет запускать блок - ExecutionBlock проанализируйте результат, запланируйте новый набор обещаний на основе массива результатов, то есть списка Spotify user объекты и выполнить новый вызов HTTP в пределах ExecutionProfileBlock асинхронно.

Затем вы можете увидеть вложенную структуру Promise, которая позволяет создавать множественные и полностью асинхронные вложенные HTTP-вызовы и объединять результаты каждого подмножества вызовов через Promise.all,

ПРИМЕЧАНИЕ Недавний Spotify search Для API потребуется указать токен доступа в заголовках запроса:

-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" />

Я подробно обсудил это решение здесь.

Короткий ответ: вы должны реализовать обратный вызов следующим образом:

function callback(response) {
    // Here you can do what ever you want with the response object.
    console.log(response);
}

$.ajax({
    url: "...",
    success: callback
});

Js однопоточный.

Браузер можно разделить на три части:

1) Event Loop

2) Веб-API

3) Очередь событий

Цикл событий выполняется вечно, то есть как бесконечный цикл. Очередь событий - это место, где вся ваша функция помещается в какое-то событие (например, щелчок). Это выполняется одна за другой из очереди и помещается в цикл событий, который выполняет эту функцию и подготавливает ее самостоятельно. для следующего за первым выполняется. Это означает, что выполнение одной функции не начинается до тех пор, пока функция в очереди не будет выполнена в цикле обработки событий.

Теперь давайте подумаем, что мы поместили две функции в очередь: одна предназначена для получения данных с сервера, а другая использует эти данные. Сначала мы поместили функцию serverRequest() в очередь, а затем функцию utiliseData(). Функция serverRequest входит в цикл обработки событий и выполняет вызов к серверу, так как мы никогда не знаем, сколько времени потребуется, чтобы получить данные с сервера, поэтому ожидается, что этот процесс займет время, и поэтому мы заняты циклом обработки событий, таким образом, подвисая нашу страницу. API вступает в роль, он берет эту функцию из цикла событий и имеет дело с тем, что сервер делает цикл событий свободным, чтобы мы могли выполнить следующую функцию из очереди. Следующая функция в очереди - это utiliseData(), который идет в цикле, но из-за отсутствия доступных данных он идет растрата и выполнение следующей функции продолжается до конца очереди (это называется асинхронным вызовом, т.е. мы можем делать что-то еще, пока не получим данные)

Предположим, что у нашей функции serverRequest() есть код возврата в коде, когда мы получаем данные из серверного веб-API и помещаем их в очередь в конце очереди. Поскольку он помещается в конец очереди, мы не можем использовать его данные, так как в нашей очереди не осталось функции для использования этих данных.Таким образом, невозможно вернуть что-либо из Async Call.

Таким образом, решением этого является обратный вызов или обещание.

Изображение из одного из ответов здесь, правильно объясняет использование обратного вызова... Мы передаем нашу функцию (функцию, использующую данные, возвращенные с сервера) функции, вызывающей сервер.

Перезвоните

 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) /questions/3693309/kak-mne-vernut-otvet-ot-asinhronnogo-vyizova/3693311#3693311

Ответ на 2017 год: теперь вы можете делать именно то, что хотите, в каждом текущем браузере и узле

Это довольно просто:

  • Вернуть обещание
  • Используйте 'await', который сообщит JavaScript, чтобы он ожидал, что обещание будет преобразовано в значение (например, HTTP-ответ)
  • Добавьте ключевое слово async в родительскую функцию

Вот рабочая версия вашего кода:

(async function(){

var response = await superagent.get('...')
console.log(response)

})()

await поддерживается во всех текущих браузерах и узле 8

Другое решение состоит в том, чтобы выполнить код через последовательного исполнителя 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. Запустите функцию синхронно через nnsynjs:

nsynjs.run(process,this,function () {
    console.log("synchronous function finished");
});

Nsynjs будет оценивать все операторы и выражения шаг за шагом, приостанавливая выполнение в случае, если результат какой-либо медленной функции не готов.

Дополнительные примеры здесь: https://github.com/amaksr/nsynjs/tree/master/examples

Вы можете использовать эту пользовательскую библиотеку (написанную с помощью 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)
});

1. Первый шаг преткновения

Как и для многих других, моя встреча с асинхронными вызовами поначалу вызвала недоумение.
Подробностей не помню, но, возможно, пробовал что-то вроде:

Все приведенные выше примеры краткие и лаконично демонстрируют, как асинхронные вызовы могут использоваться в игрушечных API. Использование небольших API-интерфейсов хорошо подходит для объяснения концепций и рабочего кода, но примеры могут быть пробелами.

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

5. Как визуализировать мэшап в Postman 6

содержит информацию об артистах и ​​музыкальных группах.
Пример - запрос британской рок-группы Coldplay :
http://musicbrainz.org/ws/2/artist/cc197bad-dc9c-440d-a5b5-d52ba2e14234?&amp;amp;fmt=json&amp;amp;inc=url-rels+release-groups .
Ответ в формате JSON содержит, среди прочего, 25 названий самых ранних альбомов группы. Эта информация находится в release-groupsмножество. Начало этого массива, включая его первый объект:

      ...
  "release-groups": [
    {
      "id": "1dc4c347-a1db-32aa-b14f-bc9cc507b843",
      "secondary-type-ids": [],
      "first-release-date": "2000-07-10",
      "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc",
      "disambiguation": "",
      "secondary-types": [],
      "title": "Parachutes",
      "primary-type": "Album"
    },
...

Этот фрагмент JSON показывает, что первый альбом Coldplay - это Parachutes . Это также дает id, в таком случае 1dc4c347-a1db-32aa-b14f-bc9cc507b843, который является уникальным идентификатором альбома.

Этот идентификатор можно использовать для поиска в :
http://coverartarchive.org/release-group/1dc4c347-a1db-32aa-b14f-bc9cc507b843 . 7

Для каждого альбома ответ JSON содержит несколько изображений, одно из которых является передней обложкой альбома. Первые несколько строк ответа на вышеуказанный запрос:

      {
  "images": [
    {
      "approved": true,
      "back": false,
      "comment": "",
      "edit": 22132705,
      "front": true,
      "id": 4086974851,
      "image": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851.jpg",
      "thumbnails": {
        "250": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg",
        "500": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
        "1200": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-1200.jpg",
        "large": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
= = >   "small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg"
    },
...

Интересна линия "small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg".
Этот URL-адрес является прямой ссылкой на обложку альбома Parachutes .

Код для создания и визуализации мэшапа

Общая задача - использовать Postman для визуализации названий альбомов и обложек музыкальной группы. Как написать код для этого уже довольно подробно описывалось в на вопрос « Как я могу визуализировать мэшап API в Postman?». - Поэтому я не буду здесь долго обсуждать и просто представлю код и скриншот результата:

      const lock = setTimeout(() => {}, 43210);
const albumsArray = [];
const urlsArray = [];
const urlOuter = 'https://musicbrainz.org/ws/2/artist/' +
  pm.collectionVariables.get('MBID') + '?fmt=json&inc=url-rels+release-groups';
pm.sendRequest(urlOuter, (_, responseO) => {
  const bandName = responseO.json().name;
  const albums = responseO.json()['release-groups'];
  for (const item of albums) {
    albumsArray.push(item.title);
    urlsArray.push('https://coverartarchive.org/release-group/' + item.id);
  }
  albumsArray.length = urlsArray.length = 15;
  const images = [];
  let countDown = urlsArray.length;
  urlsArray.forEach((url, index) => {
    asynchronousCall(url, imageURL => {
      images[index] = imageURL;
      if (--countDown === 0) { // Callback for ALL starts on next line.
        clearTimeout(lock); // Unlock the timeout.
        const albumTitles = albumsArray.map(value => ({ title: value }));
        const albumImages = images.map(value => ({ image: value }));
        const albumsAndImages = albumTitles.map(
          (item, i) => Object.assign({}, item, albumImages[i]));
        const template = `<table>
          <tr><th>` + bandName + `</th></tr>
          {{#each responseI}}
          <tr><td>{{title}}<br><img src="{{image}}"></td></tr>
          {{/each}}
        </table>`;
        pm.visualizer.set(template, { responseI: albumsAndImages });
      }
    });
  });
  function asynchronousCall (url, callback) {
    pm.sendRequest(url, (_, responseI) => {
      callback(responseI.json().images.find(obj => obj.front === true)
        .thumbnails.small); // Individual callback.
    });
  }
});


Результат и документация

Результат и документация в Postman


Как скачать и запустить Коллекцию почтальона

Запуск коллекции почтальона должен быть простым.
Предполагая, что вы используете настольную версию Postman , сделайте следующее:

  1. Загрузите и сохраните
    http://henke.atwebpages.com/postman/mbid/MusicBands.pm_coll.json
    в подходящем месте на жестком диске.

  2. В Postman Ctrl+ O> Загрузить файлы> MusicBands.pm_coll.json> Импорт .
    Теперь вы должны увидеть среди своих коллекций в Postman.

  3. Коллекции> MusicBands > DummyRequest> Отправить . 8

  4. В теле ответа почтальона нажмите « Визуализировать» .

  5. Теперь у вас должна быть возможность прокручивать 15 альбомов, как показано на скриншоте выше.

Рекомендации


1 Выражено оригинальным плакатом как: все они возвращаются undefined.
2 Если вы думаете, что асинхронные вызовы сбивают с толку, подумайте о том, чтобы взглянуть на Некоторые вопросы и ответы об асинхронных вызовахнекоторые вопросы и ответы об асинхронных вызовах, чтобы узнать, помогает ли это.
3 Имя XMLHttpRequestвводит в заблуждение, как X в AJAX - в наши дни формат данных веб-API повсеместно является JSON, а не XML.
4 ПринестиFetch возвращает Обещатьобещание . Я был удивлен, узнав, что ни XMLHttpRequest, ни Fetch не являются частью стандарта ECMAScript . Причина, по которой JavaScript может получить к ним доступ здесь, заключается в том, что веб-браузер предоставляет их. и Стандарт FetchстандартСтандарт Fetch Standard Стандарт XMLHttpRequestXMLHttpRequest поддерживаются Рабочая группа по технологиям веб-гипертекстовых приложений (WHATWG)Рабочей группой по технологиям веб-гипертекстовых приложений (WHATWG), которая была сформирована в июне 2004 года.
5 Этот раздел многое заимствует у Как я могу получить массив URL-адресов с помощью Promise.all?Как я могу получить массив URL-адресов с помощью Promise.all?.
6 Этот раздел в значительной степени зависит от того, ответекак я могу визуализировать мэшап API в Postman?Как я могу визуализировать мэшап API в Postman?.
7 Этот URL-адрес автоматически перенаправляется на: https://ia800503.us.archive.org/29/items/mbid-435fc965-9121-461e-b8da-d9b505c9dc9b/index.json .
8 Если вы получили сообщение об ошибке « Что-то пошло не так при запуске ваших скриптов» , попробуйте снова нажать « Отправить» .

ECMAScript 6 имеет "генераторы", которые позволяют легко программировать в асинхронном стиле.

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://stackru.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 или closure-compiler для генерации ECMAScript 5.

Обратный звонок ...args когда вы читаете их, они заключаются в массив и деструктурируются, чтобы шаблон мог справиться с обратными вызовами, имеющими несколько аргументов. Например, с узлом fs:

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);

Мы попадаем во вселенную, которая, кажется, движется в измерении, которое мы называем "время". Мы не очень понимаем, что такое время, но мы разработали абстракции и словарный запас, которые позволяют нам рассуждать и говорить об этом: "прошлое", "настоящее", "будущее", "до", "после".

Компьютерные системы, которые мы создаем - все больше и больше - имеют время как важное измерение. Определенные вещи созданы, чтобы произойти в будущем. Тогда другие вещи должны произойти после того, как эти первые вещи в конечном итоге произойдут. Это основное понятие называется "асинхронность". В нашем мире, который становится все более сетевым, наиболее распространенный случай асинхронности - ожидание ответа от какой-либо удаленной системы на какой-либо запрос.

Рассмотрим пример. Вы звоните молочнику и заказываете молоко. Когда это приходит, вы хотите положить его в свой кофе. Вы не можете положить молоко в свой кофе прямо сейчас, потому что его еще нет. Вы должны ждать, пока оно придет, прежде чем положить его в свой кофе. Другими словами, следующее не будет работать:

var milk = order_milk();
put_in_coffee(milk);

Потому что у JS нет возможности узнать, что нужно ждать order_milk закончить, прежде чем он выполнит put_in_coffee, Другими словами, он не знает, что order_milk является асинхронным- то, что не приведет к молоку до некоторого будущего времени. 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 действие, следующее:

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

Вот несколько подходов для работы с асинхронными запросами:

  1. Обозреватель Обещание объекта
  2. Q - библиотека обещаний для JavaScript
  3. A + Promises.js
  4. JQuery отложено
  5. API XMLHttpRequest
  6. Использование концепции обратного вызова - как реализация в первом ответе

Пример: отложенная реализация 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() Вызов выполняется асинхронно после возврата функции. Тогда проблема заключается в том, как или где хранить результаты, полученные асинхронным вызовом после его возврата.

В этой теме было дано несколько решений. Возможно, самый простой способ - передать объект 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();

Используя 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 компиляторов, если он не определен.

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

Используя ES2017, вы должны иметь это как объявление функции

async function foo() {
    var response = await $.ajax({url: '...'})
    return response;
}

И выполнить это так.

(async function() {
    try {
        var result = await foo()
        console.log(result)
    } catch (e) {}
})()

Или синтаксис обещания

foo().then(response => {
    console.log(response)

}).catch(error => {
    console.log(error)

})

Конечно, есть много подходов, таких как синхронный запрос, обещание, но из моего опыта я думаю, что вы должны использовать подход обратного вызова. Это естественно для асинхронного поведения Javascript. Итак, ваш фрагмент кода может быть переписан немного иначе:

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            myCallback(response);
        }
    });

    return result;
}

function myCallback(response) {
    // Does something.
}

Вопрос был:

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

что МОЖЕТ быть интерпретировано как:

Как заставить асинхронный код выглядеть синхронно?

Решение будет состоять в том, чтобы избежать обратных вызовов и использовать комбинацию Promises и async / await.

Я хотел бы привести пример запроса 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")
Другие вопросы по тегам