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

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

30 ответов


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

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

проблема

на 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(); должен ждать пока функция не вернет результат.

асинхронные

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

это именно то, что происходит, когда вы делаете запрос AJAX.

findItem(function(item) {
    // Do something with item
});
doSomethingElse();

вместо того, чтобы ждать ответа, выполнение продолжается немедленно и оператор после вызова Ajax выполняется. Чтобы получить ответ в конечном итоге, вы предоставляете функцию, которая будет вызываться после получения ответа, a обратный звонок (заметили что-то? обратный звонок ?). Любой оператор, поступающий после этого вызова, выполняется до обратного вызова называемый.


решение(с)

примите асинхронную природу JavaScript! хотя некоторые асинхронные операции предоставляют синхронные аналоги (как и "Ajax"), обычно не рекомендуется их использовать, особенно в контексте браузера.

почему это плохо вы спрашиваете?

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

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

в следующем мы рассмотрим три различных решения, которые строятся поверх каждого другое:

  • обещания async/await (ES2017+, доступно в старых браузерах, если вы используете транспилер или регенератор)
  • обратные вызовы (популярно в узле)
  • обещания then() (ES2015+, доступный в старых браузерах, если вы используете одну из многих библиотек promise)

все три доступны в текущих браузерах, и узел 7+.


ES2017+: обещания с async/await

версия ECMAScript, выпущенная в 2017 году, представила поддержка уровня синтаксиса для асинхронных функций. С помощью async и await, вы можете писать асинхронно в "синхронном стиле". Код по-прежнему асинхронный, но его легче читать/понимать.

async/await строит поверх обещаний:async функция всегда возвращает обещание. await "разворачивает" обещание и либо приводит к значению, с которым обещание было решено, либо выдает ошибку, если обещание было отклонено.

важно: вы можете использовать только await внутри до false. Обратите внимание, что эта опция устаревший начиная с jQuery 1.8. Затем вы можете либо использовать success обратный вызов или доступ к responseText свойства объект jqXHR:

function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}

если вы используете любой другой метод jQuery Ajax, например $.get, $.getJSON, etc. вы должны изменить его на $.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;
}

(Скрипка)

значение 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 (прежде всего прочитайте ответы от Benjamin Gruenbaum & Felix Kling)

если вы не используете 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. он использует новую загрузку (поэтому вам не нужно проверять readystate & & status)
  4. есть некоторые другие ситуации, которые я не помню, что делает XMLHttpRequest 1 раздражающим.

есть два способа получить ответ на этот вызов Ajax (три с использованием имени XMLHttpRequest var):

простейший:

this.response

или если по какой-то причине вы bind() обратный вызов a класс:

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

снова ... это очень короткая функция, но она получает & post.

примеры использования:

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 & & synchronous полностью нарушает точку ответа. Какой хороший короткий способ использовать 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 ' как formdata, он не выдаст ошибку.

в первом случае ошибка находится внутри displayAjax() под this.statusText as Method not Allowed.

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

междоменная ошибка бросков не допускается автоматически.

в ответе на ошибку нет кодов ошибок.

есть только this.type который установлен на ошибку.

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

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

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


читать файл синхронно

не делай что.

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

function omg(a, c){ // URL
  c = new XMLHttpRequest;
  c.open('GET', a, true);
  c.send();
  return c; // Or c.response
}

теперь вы можете сделать

 var res = omg('thisIsGonnaBlockThePage.txt');

нет другого способа сделать это не асинхронным способом. (Да, с циклом setTimeout... но серьезно?)

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

только если у вас есть страница где вы загружаете всегда один и тот же XML/JSON или что вам нужно только одна функция. В этом случае немного измените функцию Ajax и замените b своей специальной функцией.


вышеуказанные функции для основной пользы.

если вы хотите расширить функцию...

Да, вы можете.

я использую много API, и одна из первых функций, которые я интегрирую в каждую HTML-страницу, - это первая функция Ajax в этом ответе с GET только...

но вы можете сделать много вещей с XMLHttpRequest 2:

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

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


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

это означает AngularJS, jQuery (с отложенным), замену native XHR (fetch), 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. Этот ответ предназначен для общего использования обещания либо на frontend и backend.


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

модель параллелизма JavaScript в браузере и на сервере с NodeJS/io.js is асинхронные и реактивная.

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

это означает, когда вы возвращаетесь data the 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 is undefined С data = 5 часть еще не выполнена. Вероятно, он будет выполнен через секунду, но к этому времени он не имеет отношения к возвращаемому значение.

поскольку операция еще не произошла (AJAX, server call, IO, timer), вы возвращаете значение до того, как запрос получил возможность сообщить вашему коду, что это за значение.

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

быстрый обзор обещаний

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

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

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

когда мы называем .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);
    });
}

теперь, после того, как мы преобразовали 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 В)

ЕС6 вводит генераторы которые являются функциями, которые могут вернуться в середине, а затем возобновить точку, в которой они были. Это обычно полезно для последовательностей, например:

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

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

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

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


самое простое решение-создать функцию JavaScript и вызвать ее для Ajax success обратный.

function callServerAsync(){
    $.ajax({
        url: '...',
        success: function(response) {

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}

function foo(callback) {

    $.ajax({
        url: '...',
        success: function(response) {
           return callback(null, response);
        }
    });
}

var result = foo(function(err, result){
          if (!err)
           console.log(result);    
}); 

Я отвечу ужасным, нарисованным от руки комиксом. Второе изображение-причина, почему result is undefined в примере кода.

enter image description here


Angular1

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

здесь он говорит:

Promises можно использовать для unnest асинхронных функций и позволяет связывать несколько функций вместе.

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

пример найден в docs упоминается под.

  promiseB = promiseA.then(
    function onSuccess(result) {
      return result + 1;
    }
    ,function onError(err) {
      //Handle error
    }
  );

 // promiseB will be resolved immediately after promiseA is resolved 
 // and its value will be the result of promiseA incremented by 1.

Angular2 и позже

на Angular2 С посмотрите на следующий пример, но его рекомендовано использовать Observables С Angular2.

 search(term: string) {
     return this.http
  .get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
  .map((response) => response.json())
  .toPromise();

}

вы можете потреблять это таким образом,

search() {
    this.searchService.search(this.searchField.value)
      .then((result) => {
    this.result = result.artists.items;
  })
  .catch((error) => console.error(error));
}

посмотреть оригинал пост здесь. Но Typescript не поддерживает родной на ES6 обещания, если вы хотите использовать его, вам понадобится плагин для что.

дополнительно вот обещания spec задать здесь.


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

// WRONG
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log(results); // E.g., using them, returning them, etc.

пример:

// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log("Results:", results); // E.g., using them, returning them, etc.

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

почему это не сработает, что вызов из doSomethingAsync еще не побежал к тому времени, как вы пытаетесь использовать результаты.

Итак, если у вас есть массив (или список) и вы хотите выполнять асинхронные операции для каждой записи, у вас есть два варианта: выполнять операции параллельно (перекрывая) или последовательно (один за другим в последовательности).

параллельно

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

var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

пример:

var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(мы могли бы покончить с expecting и просто использовать results.length === theArray.length, но это оставляет нас открытыми к возможности того, что theArray изменяется во время выполнения вызовов...)

обратите внимание, как мы используем index С forEach сохранить результат в results в том же положении, что и запись, к которой он относится, даже если результаты поступают не по порядку (начиная с async звонки не обязательно в том порядке, в котором они были запущены).

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

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+ (возможно, с транспилером, таким как Бабель), вы можете использовать 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+ (пока), вы можете использовать вариант "обещание уменьшить" шаблон (это сложнее, чем обычное обещание уменьшить, потому что мы не передаем результат от одного к другому, а вместо этого собираем свои результаты в array):

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 is возвращение разрешить обещание (разрешается при возврате res.data.value). Поэтому вы ждете, пока $http.get запрос завершен, а затем


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

вот пример того же самого:

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

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


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

Если вы используете Угловое, Реагировать или любые другие структуры, которые делают два способа привязки данных, эта проблема просто исправлена для вас, поэтому в easy word ваш результат undefined на первом этапе, так что у вас есть result = undefined прежде чем вы получите данные, тогда как только вы получите результат, он будет обновлен и назначьте новое значение, которое отвечает на ваш вызов Ajax...

но как вы можете сделать это в чистом в JavaScript или jQuery например, как вы задали этот вопрос?

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

например, в вашем случае, который вы используете 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
});

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


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

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
}

вы можете проверить проект здесь.


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

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

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

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

  • обрабатывать асинхронные HTTP-запросы;
  • дождитесь ответа от каждого вызова API;
  • использовать обещание шаблон;
  • Использовать Обещание.Весь шаблон для присоединения нескольких HTTP-вызовов;

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


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

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

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

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

(async function(){

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

})()

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


вы можете использовать эту пользовательскую библиотеку (написанную с помощью Promise) для удаленного вызова.

function $http(apiConfig) {
    return new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open(apiConfig.method, apiConfig.url);
        client.send();
        client.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                // Performs the function "resolve" when this.status is equal to 2xx.
                // Your logic here.
                resolve(this.response);
            }
            else {
                // Performs the function "reject" when this.status is different than 2xx.
                reject(this.statusText);
            }
        };
        client.onerror = function () {
            reject(this.statusText);
        };
    });
}

простой пример использования:

$http({
    method: 'get',
    url: 'google.com'
}).then(function(response) {
    console.log(response);
}, function(error) {
    console.log(error)
});

другое решение-выполнить код с помощью последовательного исполнителя nsynjs.

если основная функция promisified

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>

если базовая функция-это не promisified

Шаг 1. Функция Wrap с обратным вызовом в nsynjs-aware wrapper (если она имеет обещанную версию, вы можете пропустить этот тест):

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


Js является однопоточным.

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

1)Цикл Обработки Событий

2) Web API

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

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

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

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

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

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

CallBack

 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 Верхний) https://stackoverflow.com/a/14220323/7579856


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

  1. объект обещания браузера
  2. Q - библиотека обещаний для JavaScript
  3. A+ Обещает.js
  4. в jQuery отложенные
  5. XMLHttpRequest API
  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();

это очень распространенная проблема, с которой мы сталкиваемся, борясь с "тайнами" 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), но он запускает таймер, который выполняет функцию после 1s, чтобы вернуть "wohoo". Но как вы можете видеть, значение, назначенное bar, - это немедленно возвращаемый материал из foo (), а не что-либо другое, что приходит позже.

Итак, как мы решим эту проблему?

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

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

таким образом, резюме - to решите асинхронные функции, такие как вызовы на основе ajax и т. д. вы можете использовать обещание resolve значение (которое вы собираетесь вернуть). Таким образом, короче разрешить вместо возвращение в асинхронные функции.

обновление (обещания с асинхронным/await)

Помимо использования then/catch для работы с обещаниями, существует еще один подход. Идея распознать асинхронную функцию а то ждать за обещания чтобы решить, прежде чем перейти к следующей строке кода. Это все еще просто promises под капотом, но с другим синтаксическим подходом. Чтобы сделать вещи яснее, вы можете найти сравнение ниже:

затем / catch версия:

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

использовать


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

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
    console.log("response is:", response);

    // examples of other things you can do
    yield setTimeout(callback, 1000);
    console.log("it delayed for 1000ms");
    while (response.statusText === "error") {
        [response] = yield* anotherGenerator();
    }
}

для запуска вышеуказанного кода Вы делаете следующее:

const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function

Если вам нужны целевые браузеры, которые не поддерживают ES6, вы можете запустить код через Babel или closure-compiler для создания ECMAScript 5.

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

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

короткий ответ:: ваш 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.


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

function foo() {
    var result;

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

    return result;
}

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

вопрос:

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

который можно интерпретировать как:

как сделать асинхронные код синхронно?

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

Я хотел бы привести пример для Ajax запрос.

(хотя он может быть написан на Javascript, я предпочитаю писать его на Python и компилировать его на Javascript с помощью Transcrypt. Это будет достаточно ясно.)

позволяет сначала включить использование JQuery, чтобы иметь $ доступно как S:

__pragma__ ('alias', 'S', '$')

определите функцию, которая возвращает обещание, в этом случае вызов 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")

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

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

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

потому что JS не может знать, что ему нужно ждать на order_milk чтобы закончить, прежде чем он выполняет put_in_coffee. Другими словами, он не знает, что order_milk is асинхронные--это то, что не приведет к молоку до некоторого будущего времени. JS и другие декларативные языки выполняют один оператор за другим без ожидания.

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

order_milk(put_in_coffee);

order_milk стартует, заказывает молоко, затем, когда и только когда он прибывает, он вызывает put_in_coffee.

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

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

куда я иду put_in_coffee как молоко, положить в него, а также действия (drink_coffee) выполнить как только молоко было положено внутри. Такой код становится трудно писать, читать и отлаживать.

в этом случае, мы могли бы переписать код в вопрос как:

var answer;
$.ajax('/foo.json') . done(function(response) {
  callback(response.data);
});

function callback(data) {
  console.log(data);
}

введите обещания

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

в случае нашего молока и кофе, мы конструируем order_milk чтобы вернуть обещание на поступление молока, укажите put_in_coffee как a 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 $.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 это? Оказывается, есть ... --33--> ключевое слово, используемое внутри специального типа функции, называемой" асинхронной " функцией. Эта функция является частью предстоящей версии 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);
}

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

  1. ваша точка(ы) входа выполняется в результате события. Для например, в браузер загружается тег скрипта с кодом. (Соответственно, именно поэтому вам может потребоваться беспокоит готовность страницы к запуску кода, если для этого требуются элементы dom быть построенным первым и т. д.)
  2. ваш код выполняется до завершения, однако много асинхронных вызовов это делает--без выполнения любой ваших обратных вызовов, включая XHR запросы, тайм-ауты, обработчики событий dom и т. д. Каждый из этих обратных вызовов, ожидающих выполнения, будет сидеть в очереди, ожидая своей очереди для запуска после завершения других событий, которые были запущены исполнение.
  3. каждый отдельный обратный вызов запроса XHR, тайм-аут или dom событие, вызванное один раз, будет выполняться до завершения.

хорошая новость заключается в том, что если вы хорошо понимаете этот момент, вам никогда не придется беспокоиться об условиях гонки. Прежде всего, вы должны понять, как вы хотите организовать свой код как ответ на различные дискретные события и как вы хотите объединить их в логическую последовательность. Вы можете использовать обещания или новый async/await более высокого уровня в качестве инструментов для этого, или вы можете свернуть свой собственный.

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


вместо того, чтобы бросать код на вас, есть 2 понятия, которые являются ключевыми для понимания того, как JS обрабатывает обратные вызовы и асинхронность. (это вообще слово?)

цикл событий и модель параллелизма

есть три вещи, которые вы должны знать; очереди; событие цикла и стек

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

while (queue.waitForMessage()) {
   queue.processNextMessage();
}

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

 1. call foo.com/api/bar using foobarFunc
 2. Go perform an infinite loop
 ... and so on

когда одно из этих сообщений собирается выполнить, оно выводит сообщение из очереди и создает стек, стек-это все, что JS должен выполнить, чтобы выполните инструкцию в сообщении. Поэтому в нашем примере ему говорят позвонить foobarFunc

function foobarFunc (var) {
  console.log(anotherFunction(var));
}

так что все, что foobarFunc должен выполнить (в нашем случае anotherFunction) будет нажата на стек. выполняется, а затем забывается - цикл событий затем перейдет на следующую вещь в очереди (или прослушивает сообщения)

ключевым моментом здесь является порядок исполнения. Это

когда что-то собирается запустить

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

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

вот почему с асинхронной функциональностью мы используем вещи, называемые обратные вызовы. Это похоже на обещание в буквальном смысле. Как в I обещайте вернуть что-то в какой-то момент jQuery использует определенные обратные вызовы deffered.done deffered.fail и deffered.always (среди прочих). Вы можете увидеть их все здесь

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

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

function foo(bla) {
  console.log(bla)
}

так что большую часть времени (но не всегда) вы будете проходить foo не foo()

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