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

3553

У меня есть функция 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`.
задан Felix Kling 08 янв. '13 в 20:06
источник

25 ответов

3992

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

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

Проблема

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

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

Синхронный

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

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

function findItem() {
    var item;
    while(item_not_found) {
        // search
    }
    return item;
}

var item = findItem();

// Do something with item
doSomethingElse();

Даже если findItem может занять много времени, любой код, следующий после var item = findItem();, должен ждать, пока функция вернет результат.

Асинхронный

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

То, что происходит, когда вы делаете запрос Ajax.

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

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



Решение (s)

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

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

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

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

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

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

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


ES2017 +: Promises с async/await

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

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

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

Вы можете узнать больше о async и await на MDN.

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

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

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


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

// Async functions always return a promise
getAllBooks()
  .then(function(books) {
    console.log(books);
  });

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


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

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

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

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

становится

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

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

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

foo(myCallback);

foo определяется следующим образом:

function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}

callback будет ссылаться на функцию, которую мы передаем на foo, когда мы ее называем, и просто передаем ее на success. То есть как только запрос Ajax будет успешным, $.ajax вызовет callback и передаст ответ на обратный вызов (на который можно ссылаться на result, так как именно так мы определили обратный вызов).

Вы также можете обработать ответ, прежде чем передавать его на обратный вызов:

function foo(callback) {
    $.ajax({
        // ...
        success: function(response) {
            // For example, filter the response
            callback(filtered_response);
        }
    });
}

Легче писать код с помощью обратных вызовов, чем может показаться. В конце концов, JavaScript в браузере сильно управляется событиями (события DOM). Получение ответа Ajax - это не что иное, как событие.
Трудности могут возникнуть, когда вам придется работать со сторонним кодом, но большинство проблем можно решить, просто продумав поток приложений.


ES2015 +: Promises с then()

Promise API - новая функция ECMAScript 6 (ES2015), но она имеет хороший поддержка браузера. Существует также множество библиотек, которые реализуют стандартный API Promises и предоставляют дополнительные методы для облегчения использования и составления асинхронных функций (например, bluebird).

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

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

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

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 мы могли бы использовать Promises следующим образом:

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

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

Дополнительная информация о promises: камни HTML5 - JavaScript Promises

Боковое примечание: jQuery отложенные объекты

Отложенные объекты - это пользовательская реализация jQuery Promises (до того, как API Promise был стандартизован). Они ведут себя почти как Promises, но выставляют несколько иной API.

Каждый метод Ajax jQuery уже возвращает "отложенный объект" (на самом деле обещание отложенного объекта), который вы можете просто вернуть из своей функции:

function ajax() {
    return $.ajax(...);
}

ajax().done(function(result) {
    // Code depending on result
}).fail(function() {
    // An error occurred
});

Боковое примечание: Promise gotchas

Имейте в виду, что Promises и отложенные объекты являются просто контейнерами для будущего значения, они не являются самим значением. Например, предположим, что у вас есть следующее:

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

Heads up! Невозможно выполнить синхронный JSONP запрос. JSONP по своей природе всегда асинхронен (еще одна причина, чтобы даже не учитывать этот вариант).

ответ дан Felix Kling 08 янв. '13 в 20:06
источник
726

Если вы не используете 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 или promises, я добавил еще один ответ ниже)


То, с чем вы сталкиваетесь

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

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

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

Простая аналогия

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

(Fiddle)

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

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

function onComplete(a){ // When the code completes, do this
    alert(a);
}

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

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

Использование:

getFive(onComplete);

Который должен предупредить "5" на экране. (Fiddle).

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

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

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

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

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

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

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

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

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

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

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

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

ответ дан Benjamin Gruenbaum 30 мая '13 в 2:30
источник
223

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

Как вы можете видеть:

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

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

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

Обработчики ошибок могут быть полезны, если вы устанавливаете пользовательские заголовки, устанавливаете responseType для буфера памяти или любого другого....

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

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

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

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

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

В ответе об ошибке отсутствуют коды ошибок.

Существует только this.type, для которого установлена ​​ошибка.

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

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

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


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

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

Если вы хотите заблокировать браузер на некоторое время, загрузите хороший большой файл 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 специальной функцией.


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

Если вы хотите EXTEND функцию...

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

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

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

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

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

ответ дан cocco 19 авг. '13 в 11:06
источник
208

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

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

Ваш код должен быть чем-то вроде этого:

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


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

Модель JavaScript concurrency в браузере и на сервере с NodeJS/io.js является асинхронной и реактивной.

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

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

Вот простая аналогия проблемы:

    function getFive(){
        var data;
        setTimeout(function(){ // set a timer for one second in the future
           data = 5; // after a second, do this
        }, 1000);
        return data;
    }
    document.body.innerHTML = getFive(); // `undefined` here and not 5

Значение data равно undefined, так как часть data = 5 еще не выполнена. Скорее всего, это произойдет через секунду, но к тому времени это не имеет отношения к возвращенному значению.

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

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

Краткое описание promises

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

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

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

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

С promises

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

function delay(ms){ // takes amount of milliseconds
    // returns a new promise
    return new Promise(function(resolve, reject){
        setTimeout(function(){ // when the time is up
            resolve(); // change the promise to the fulfilled state
        }, ms);
    });
}

Теперь, после преобразования setTimeout в использование promises, мы можем использовать 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;
});

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

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

ES2015 (ES6)

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

function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
    yield 1;
    yield 2;
    while(true) yield 3;
}

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

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

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

Он по-прежнему возвращает обещание точно так же:)

ответ дан Benjamin Gruenbaum 02 дек. '17 в 21:29
149

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

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

ответ дан Nic 23 мая '14 в 5:05
источник
143

Самое простое решение - создать функцию 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);    
}); 
ответ дан Hemant Bavle 18 февр. '14 в 21:58
источник
88

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

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

ответ дан Johannes Fahrenkrug 11 авг. '16 в 17:17
источник
70

Angular1

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

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

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

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

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

Кроме того, здесь описывается promises spec.

ответ дан Maleen Abewardana 26 авг. '14 в 11:11
источник
51

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

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

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

Parallel

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

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

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

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

Серия

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

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});

(Поскольку мы выполняем работу последовательно, мы можем просто использовать results.push(result), поскольку мы знаем, что мы не получим результаты не в порядке. В приведенном выше примере мы могли бы использовать results[index] = result;, но в некоторых из в следующих примерах мы не используем индекс.)

Пример:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

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

(Или, опять же, создайте обертку для doSomethingAsync, которая даст вам обещание и сделайте следующее...)

Если doSomethingAsync дает вам обещание, если вы можете использовать синтаксис ES2017 + (возможно, с транспилером, например Babel), вы можете используйте async функцию с for-of и await:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

Пример:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

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

Если вы не можете использовать синтаксис ES2017 + (пока), вы можете использовать вариацию в шаблоне Promise reduce (это сложнее обычного Promise уменьшить, потому что мы не передаем результат от одного к другому, а собираем результаты в массиве):

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;
}
ответ дан T.J. Crowder 03 мая '17 в 19:59
источник
45

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

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope,$http) {

    var getJoke = function(){
        return $http.get('http://api.icndb.com/jokes/random').then(function(res){
            return res.data.value;  
        });
    }

    getJoke().then(function(res) {
        console.log(res.joke);
    });
});

Как вы видите, getJoke возвращает разрешенное обещание (оно разрешается при возврате res.data.value). Таким образом, вы дождались завершения запроса $http.get, а затем console.log(res.joke) (как обычный асинхронный поток).

Это plnkr:

http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/

ответ дан Fran Carmona 02 июня '16 в 11:31
источник
41

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

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

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

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

ответ дан jsbisht 02 сент. '15 в 15:54
источник
33

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

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

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

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
}

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

ответ дан rohithpr 25 янв. '16 в 20:43
источник
33

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

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

$.ajax({
    url: "...",
    success: callback
});
ответ дан Pablo Matias Gomez 22 апр. '16 в 17:47
источник
27

Вы можете использовать эту пользовательскую библиотеку (написанную с помощью 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)
});
ответ дан Vinoth Rajendran 26 мая '16 в 16:26
источник
27

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

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

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

Context. В этом примере запрашивается конечная точка Spotify Web API для поиска объектов playlist для заданного набора строк запроса:

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

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

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

Примечание В последних API-интерфейсах Spotify search для заголовков запросов должен быть указан токен доступа:

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

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

var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
    log: function(s) {
        document.getElementById("console").innerHTML += s + "<br/>"
    }
}

// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
    call: function(what, response) {
        var request;
        if (window.XMLHttpRequest) { // Mozilla, Safari, ...
            request = new XMLHttpRequest();
        } else if (window.ActiveXObject) { // Internet Explorer
            try {
                request = new ActiveXObject('Msxml2.XMLHTTP');
            }
            catch (e) {
                try {
                  request = new ActiveXObject('Microsoft.XMLHTTP');
                } catch (e) {}
            }
        }

        // State changes
        request.onreadystatechange = function() {
            if (request.readyState === 4) { // Done
                if (request.status === 200) { // Complete
                    response(request.responseText)
                }
                else
                    response();
            }
        }
        request.open('GET', what, true);
        request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
        request.send(null);
    }
}

//PromiseAll
var promiseAll = function(items, block, done, fail) {
    var self = this;
    var promises = [],
                   index = 0;
    items.forEach(function(item) {
        promises.push(function(item, i) {
            return new Promise(function(resolve, reject) {
                if (block) {
                    block.apply(this, [item, index, resolve, reject]);
                }
            });
        }(item, ++index))
    });
    Promise.all(promises).then(function AcceptHandler(results) {
        if (done) done(results);
    }, function ErrorHandler(error) {
        if (fail) fail(error);
    });
}; //promiseAll

// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
    var url = "https://api.spotify.com/v1/"
    url += item;
    console.log( url )
    SimpleRequest.call(url, function(result) {
        if (result) {

            var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
                return item.owner.href;
            })
            resolve(profileUrls);
        }
        else {
            reject(new Error("call error"));
        }
    })
}

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

promiseAll(arr, function(item, index, resolve, reject) {
    console.log("Making request [" + index + "]")
    ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results

    console.log("All profiles received " + results.length);
    //console.log(JSON.stringify(results[0], null, 2));

    ///// promiseall again

    var ExecutionProfileBlock = function(item, index, resolve, reject) {
        SimpleRequest.call(item, function(result) {
            if (result) {
                var obj = JSON.parse(result);
                resolve({
                    name: obj.display_name,
                    followers: obj.followers.total,
                    url: obj.href
                });
            } //result
        })
    } //ExecutionProfileBlock

    promiseAll(results[0], function(item, index, resolve, reject) {
        //console.log("Making request [" + index + "] " + item)
        ExecutionProfileBlock(item, index, resolve, reject);
    }, function(results) { // aggregated results
        console.log("All response received " + results.length);
        console.log(JSON.stringify(results, null, 2));
    }

    , function(error) { // Error
        console.log(error);
    })

    /////

  },
  function(error) { // Error
      console.log(error);
  });
<div id="console" />

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

ответ дан loretoparisi 13 апр. '16 в 1:55
источник
22

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

Если основная функция обеимитирована

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

function synchronousCode() {

    var getURL = function(url) {
        return window.fetch(url).data.text().data;
    };
    
    var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
    console.log('received bytes:',getURL(url).length);
    
};

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

Если базовая функция не является многообещающей

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

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

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

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

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

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

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

Другие примеры здесь: https://github.com/amaksr/nsynjs/tree/master/examples

ответ дан amaksr 27 мая '17 в 5:47
источник
18

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

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

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

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

(async function(){

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

})()

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

ответ дан mikemaccana 02 июня '17 в 12:51
источник
17

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

Пример: 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();
ответ дан Mohan Dere 13 авг. '16 в 12:36
источник
16

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

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

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

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

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

ответ дан Alireza 24 мая '17 в 12:38
источник
12

Короткий ответ. Ваш метод foo() возвращается немедленно, а вызов $ajax() выполняется асинхронно после возвращения функции. Проблема заключается в том, как и где хранить результаты, полученные при вызове async, после его возврата.

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

ответ дан David R Tribble 24 сент. '15 в 1:52
источник
8

Используйте функцию callback() внутри успеха foo(). Попробуйте таким образом. Это просто и легко понять.  

var lat = "";
var lon = "";
function callback(data) {
    lat = data.lat;
    lon = data.lon;
}
function getLoc() {
    var url = "http://ip-api.com/json"
    $.getJSON(url, function(data) {
        callback(data);
    });
}

getLoc();
ответ дан Mahfuzur Rahman 24 апр. '17 в 11:09
источник
5

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

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

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

function foo(){
   return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
    setTimeout ( function(){ 
      // promise is RESOLVED , when exececution reaches this line of code
       resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
    }, 1000 )
  })
}

let bar ; 
foo().then( res => {
 bar = res;
 console.log(bar) // will print 'wohoo'
});

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

ответ дан Anish K. 31 окт. '17 в 23:12
источник
3

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

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}
ответ дан Khoa Bui 05 июля '17 в 23:28
источник
2

Сначала посмотрим на лес, прежде чем смотреть на деревья.

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

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

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

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

ответ дан Haim Zamir 25 окт. '17 в 6:22
источник
-3

Чтобы обрабатывать любые ajax, обещания, setTimeout проверить обещание в JavaScript. $. Отказано в jQuery. Вот как вы можете справиться с ответом ответа.

function foo() {

  var result = $.ajax({
    url: '...',
    // This success callback is not required. You can remove it. 
    // Since we are getting at (1)
    success: function (response) {
      // result = response;
    }
  });

  return result;
}

// Since this is a Promise object called $Deferred in jQuery
// You can attach a then callback which accepts two callback
// first one success and second one error


foo().then(
  function( response ) {
    // This is success callback (1)
    console.log('success: ', response);
  }, function( xhr ) {
    // This is error callback
    console.log('error: ', xhr);
  }
)
ответ дан Rajkeshwar Prasad 13 окт. '17 в 10:12
источник

Ознакомьтесь с другими вопросами по меткам