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

У меня есть функция 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`.
Решение
  • & rarr; Для более общего объяснения поведения асинхрон с различными примерами, пожалуйста, смотрите * https://stackoverflow.com/q/23667086/218196
  • & rarr; Если вы уже понимаете проблему, перейдите к возможным решениям ниже.*

Проблема

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


Решение (я)

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

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

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

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

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

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

  • Обратные вызовы (популярны в узле)

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

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


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

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

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

Важно: Вы можете использовать await только внутри функции async. Прямо сейчас await верхнего уровня еще не поддерживается, поэтому вам, возможно, придется создать async IIFE (Немедленно вызванное выражение функции), чтобы начать контекст async.

Вы можете прочитать больше о async и avait на MDN .

Вот пример, который основан на верхней части задержки выше:

// Using 'superagent' which will return a promise.
var superagent = require('superagent')

// This is isn't declared as `async` because it already returns a promise
function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}

async function getAllBooks() {
  try {
    // GET a list of book IDs of the current user
    var bookIDs = await superagent.get('/user/books');
    // wait for 3 seconds (just for the sake of this example)
    await delay();
    // GET information about each book
    return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

// Start an IIFE to use `await` at the top level
(async function(){
  let books = await getAllBooks();
  console.log(books);
})();

Текущие версии browser и node поддерживают async / await. Вы также можете поддерживать старые среды, преобразуя свой код в ES5 с помощью регенератора (или инструментов, использующих регенератор, таких как Babel).


Пусть функции принимают обратные вызовы

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

В примере вопроса вы можете заставить foo принять обратный вызов и использовать его как обратный вызов success. Так что

var result = foo();
// Code that depends on 'result'

становится

foo(function(result) {
    // Code that depends on 'result'
});

Здесь мы определили функцию «встроенный», но вы можете передать любую ссылку на функцию:

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), но она уже имеет хорошую [поддержку браузера][10]. Есть также много библиотек, которые реализуют стандартный 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 broch - JavaScript Promises

Примечание: отложенные объекты 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 () не замораживает код при проверке страницы '/ password' на вашем сервере - он отправляет запрос на сервер и, пока он ожидает, немедленно возвращает объект 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 по своей природе всегда асинхронен (еще одна причина, чтобы даже не рассматривать этот вариант).

[10]: http://caniuse.com/#feat= обещает "caniuse"

Комментарии (35)

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

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


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

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

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

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

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

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

[(Скрипка)][2]

Значение a returned является неопределенным, поскольку часть a = 5 еще не выполнена. AJAX действует следующим образом, вы возвращаете значение до того, как сервер получит возможность сообщить вашему браузеру, что это за значение.

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

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» на экран. [(Скрипка)][4].

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

Есть в основном два способа, как решить это:

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

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

Теперь давайте определим самого Фу, чтобы действовать соответственно

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

[(скрипка)][6]

Теперь мы заставили нашу функцию foo принять действие, которое будет запущено после успешного завершения AJAX, мы можем расширить его, проверив, не равен ли статус ответа 200, и действуя соответствующим образом (создать обработчик сбоев и т. Д.). Эффективно решая нашу проблему.

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

Комментарии (4)

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 (три с использованием имени var 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);

Как видите, я не реализовал синхронизацию... это плохо.

Сказав это ... почему бы не сделать это простым способом?


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

Обработчики ошибок могут быть полезны, если вы установите пользовательские заголовки, установите для reponseType буфер массива BLOB-объектов или что-то еще...

Даже если вы передадите «POSTAPAPAP» как метод, он не вызовет ошибку.

Даже если вы передадите 'fdggdgilfdghfldj' как formdata, это не приведет к ошибке.

В первом случае ошибка находится внутри displayAjax () в this.statusText как Метод не разрешен.

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

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

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

Существует только 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:

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

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

Комментарии (7)

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

Это означает 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 - asynchronous и reactive.

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

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

Вот простая аналогия по этому вопросу: & Лт;!- начать фрагмент: js скрыть: false - >

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

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

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

Быстро подведем итоги обещаний

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

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

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

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

С обещаниями

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

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] (stackoverflow.com/questions/22519784/how-do-i-convert-an-существующий обратный вызов-api-to-promises) для использования обещаний, мы можем использовать then, чтобы сделать это считать:

& Лт;!- начать фрагмент: js скрыть: false - >

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

& Лт;!- конец фрагмента - >

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

Является функцией, которая возвращает iterator по последовательности 1,2,3,3,3,....который может быть повторяемым. Хотя это интересно само по себе и открывает пространство для большой возможности, есть один конкретный интересный случай.

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

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

Это все еще возвращает обещание точно так же :)

Комментарии (1)

Вы используете 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);
    }
});

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

Комментарии (4)

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

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

Комментарии (4)

Angular1

Для людей, которые используют AngularJS, могут справиться с этой ситуацией, используя «Обещания».

Здесь это говорит

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

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

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

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

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

Angular2 и позже

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

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

}

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

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

Смотрите оригинал пост здесь. Но Typescript не поддерживает native es6 Promises, если вы хотите использовать его, вам может понадобиться плагин для этого.

Кроме того, здесь есть обещания spec, определяемые здесь.

Комментарии (3)

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

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

Пример:

& Лт;!- начать фрагмент: js hide: истинная консоль: истинная павиана: ложная - >

// 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 еще не запущены к тому времени, когда вы пытаетесь использовать результаты.

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

Параллельно

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

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

Пример:

& Лт;!- начать фрагмент: js hide: истинная консоль: истинная павиана: ложная - >

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

& Лт;!- конец фрагмента - >

  • (Мы могли бы покончить с «ожиданием» и просто использовать «results.length === theArray.length», но это оставляет нас открытыми для возможности изменения «Array», когда звонки являются выдающимися...) *

Обратите внимание, как мы используем «index» из «forEach», чтобы сохранить результат в «результатах» в том же положении, что и запись, к которой он относится, даже если результаты приходят не по порядку (поскольку вызовы 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);
});

Пример:

& Лт;!- начать фрагмент: js hide: истинная консоль: истинная павиана: ложная - >

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

& Лт;!- конец фрагмента - >

Или вот версия, возвращающая «Обещание» вместо:

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, чтобы отклонить обещание, когда мы получили ошибку.) *

Пример:

& Лт;!- начать фрагмент: js hide: истинная консоль: истинная павиана: ложная - >

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);
    }));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Если вы знаете, что doSomethingAsync будет игнорировать второй и третий аргумент, вы можете просто передать его непосредственно в map (map вызывает обратный вызов с тремя аргументами, но большинство людей используют только первый в большинстве случаев): ,

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

Пример:

& Лт;!- начать фрагмент: js hide: истинная консоль: истинная павиана: ложная - >

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

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

& Лт;!- конец фрагмента - >

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

Серия

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

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});
  • (Поскольку мы выполняем работу в серии, мы можем просто использовать results.push (result), поскольку мы знаем, что не получим результаты из строя. В приведенном выше мы могли бы использовать results [index] = result;, но в некоторых из следующих примеров у нас нет индекса для использования.) *

Пример:

& Лт;!- начать фрагмент: js hide: истинная консоль: истинная павиана: ложная - >

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

Пример:

& Лт;!- начать фрагмент: js hide: истинная консоль: истинная павиана: ложная - >

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

Пример:

& Лт;!- начать фрагмент: js hide: истинная консоль: истинная павиана: ложная - >

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

Пример:

& Лт;!- начать фрагмент: js hide: истинная консоль: истинная павиана: ложная - >

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

& Лт;!- конец фрагмента - >

Комментарии (3)

Посмотрите на этот пример:

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 возвращает a dolided обещание (оно разрешается при возврате res.data.value). Таким образом, вы ждете, пока не будет выполнен запрос $ http.get , а затем выполняется console.log (res.joke) (как обычный асинхронный поток).

Это plnkr:

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);
  });
})();
Комментарии (0)

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

Так что, если вы используете Angular, Реагируйте или любые другие фреймворки, которые связывают данные двумя способами или хранят концепцию , эта проблема просто исправлена для вас, так легко, ваш результат "не определен" на первом этапе, поэтому у вас есть result = undefined до получения данных, затем, как только вы получите результат, он будет обновлен и будет присвоен новому значению, которое отвечает на ваш вызов Ajax...

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

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

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

Комментарии (3)

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

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

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

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

Комментарии (1)

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

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

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

async1(name, callback) {
  if (name)
    callback(name)
  else {
    doSomething(callback)
  }
}

async1(name, async2)

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

«Фиберс» помогает в решении проблемы.

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
}

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

Комментарии (4)

Краткий ответ: Вы должны реализовать обратный вызов, как это:

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

$.ajax({
    url: "...",
    success: callback
});
Комментарии (0)

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

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

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

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

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

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

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

ПРИМЕЧАНИЕ Недавние API-интерфейсы Spotify search потребуют, чтобы в заголовках запросов был указан токен доступа:

-H "Authorization: Bearer {your access token}" 

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

& Лт;!- начать фрагмент: js скрыть: false - >

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

& Лт;!- конец фрагмента - >

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

Комментарии (0)

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

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

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

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

(async function(){

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

})()

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

Комментарии (4)

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

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

Давайте попросим нашу функцию для PROMISE . 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'
});

Таким образом, сводка - решать асинхронные функции, такие как вызовы на основе ajax и т. Д.Вы можете использовать обещание «разрешить» значение (которое вы намерены вернуть). Таким образом, короче говоря, вы разрешаете значение вместо returning в асинхронных функциях.

ОБНОВЛЕНИЕ (Обещания с асинком / приманкой)

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

тогда / поймать версию:

function saveUsers(){
     getUsers()
      .then(users => {
         saveSomewhere(users);
      })
      .catch(err => {
         throw err;
       })
 }

версия async / await:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        throw err;
     }
  }
Комментарии (2)

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

Другое решение - выполнить код через последовательный исполнитель nsynjs.

Если основная функция упрощена

nsynjs оценит все обещания последовательно и поместит результат обещания в свойство data:

& Лт;!- начать фрагмент: js hide: ложная консоль: истинная павиана: false - >

function synchronousCode() {

    var getURL = function(url) {
        return window.fetch(url).data.text().data;
    };

    var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
    console.log('received bytes:',getURL(url).length);

};

nsynjs.run(synchronousCode,{},function(){
    console.log('synchronousCode done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

& Лт;!- конец фрагмента - >

Если базовая функция не упрощена

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

var ajaxGet = function (ctx,url) {
    var res = {};
    var ex;
    $.ajax(url)
    .done(function (data) {
        res.data = data;
    })
    .fail(function(e) {
        ex = e;
    })
    .always(function() {
        ctx.resume(ex);
    });
    return res;
};
ajaxGet.nsynjsHasCallback = true;

Шаг 2. Поместите синхронную логику в функцию:

function process() {
    console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}

Шаг 3. Запустите функцию синхронно через nsynjs:

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

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

Больше примеров здесь: https://github.com/amaksr/nsynjs/tree/master/examples

Комментарии (1)

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

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

  1. Event Loop

  2. Веб API

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

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

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

Предположим, что наша функция 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) https://stackoverflow.com/a/14220323/7579856

Комментарии (0)