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


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

4205


источник


Ответы:


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

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

Проблема

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

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

синхронный

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

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

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

var item = findItem();

// Do something with item
doSomethingElse();

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

Асинхронный

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

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

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

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


Решение (s)

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

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

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

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

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

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

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


ES2017 +: Обещает с async/await

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

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

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

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

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

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

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


async function getAllBooks() {
  try {
    // GET a list of book IDs of the current user
    var bookIDs = await superagent.get('/user/books');
    // wait for 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);
  });

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


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

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

В примере вопроса вы можете сделать 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 +: Обещает с тогда()

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

function ajax(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open('GET', url);
    xhr.send();
  });
}

ajax("/echo/json")
  .then(function(result) {
    // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });

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

Дополнительная информация о обещаниях: Скалы HTML5 - обещания JavaScript

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

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

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

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

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

Сторона примечания: Promise gotchas

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

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 по самой своей природе всегда асинхронен (еще одна причина не учитывать этот вариант).


4572



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

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


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

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

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

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

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

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

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

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

getFive(onComplete);

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

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

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

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

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

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

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

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

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

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

if (request.status === 200) {// That's HTTP for 'ok'
  console.log(request.responseText);
}

2. Код реструктуризации

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

Так:

var result = foo();
// code that depends on `result` goes here

становится:

foo(function(result) {
    // code that depends on `result`
});

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

function myHandler(result) {
    // code that depends on `result`
}
foo(myHandler);

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


869



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

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

Простейший:

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

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

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


Как упоминалось в комментарии, использование ошибки && synchronous полностью нарушает точку ответа. Какой хороший способ использовать Ajax правильно?

Обработчик ошибок

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.onerror = error;
  c.send(d||null)
}

function error(e){
  console.log('--Error--', this.type);
  console.log('this: ', this);
  console.log('Event: ', e)
}
function displayAjax(e){
  console.log(e, this);
}
x('WRONGURL', displayAjax);

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

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

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

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

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

В первом случае ошибка находится внутри displayAjax()под this.statusTextв виде 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, файловой системы), различные конвертеры изображений, использующие холст, заполняющие базы данных websql с base64images и многое другое ... Но в этих случаях вы должны создать функцию только для этой цели ... иногда вам нужны blob, буферы массивов, вы можете устанавливать заголовки, переопределять mimetype и есть намного больше ...

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


292



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

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

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

function foo() {
    var data;
    // or $.get(...).then, or request(...).then, or query(...).then
    fetch("/echo/json").then(function(response){
        data = response.json();
    });
    return data;
}

var result = foo(); // result is always undefined no matter what.

Феликс Клинг отлично справился с написанием ответа для людей, использующих jQuery с обратными вызовами для AJAX. У меня есть ответ для родного XHR. Этот ответ предназначен для общего использования обещаний либо на интерфейсе, либо на бэкэнде.


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

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

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

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

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

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

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

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

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

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

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

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

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

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

С обещаниями

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

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

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

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

function getFive(){
  // we're RETURNING the promise, remember, a promise is a wrapper over our value
  return delay(100).then(function(){ // when the promise is ready
      return 5; // return the value 5, promises are all about return values
  })
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){ 
   document.body.innerHTML = five;
});

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

Применение этого

Это то же самое для вашего первоначального вызова API, вы можете:

function foo() {
    // RETURN the promise
    return fetch("/echo/json").then(function(response){
        return response.json(); // process it inside the `then`
    });
}

foo().then(function(response){
    // access the value inside the `then`
})

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

ES2015 (ES6)

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

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

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

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

Этот несколько сложный, но очень мощный трюк позволяет синхронно писать асинхронный код. Есть несколько «бегунов», которые делают это за вас, написав один из них - несколько коротких строк кода, но выходит за рамки этого ответа. Я буду использовать Bluebird's Promise.coroutineздесь, но есть и другие обертки, такие как coили Q.async,

var foo = coroutine(function*(){
    var data = yield fetch("/echo/json"); // notice the yield
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
});

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

var main = coroutine(function*(){
   var bar = yield foo(); // wait our earlier coroutine, it returns a promise
   // server call done here, code below executes when done
   var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
   console.log(baz); // runs after both requests done
});
main();

ES2016 (ES7)

В ES7 это стандартизировано, сейчас есть несколько предложений, но во всех них вы можете awaitОбещаю. Это просто «сахар» (более сильный синтаксис) для предложения ES6 выше, добавив asyncа также awaitключевые слова. Приведем пример:

async function foo(){
    var data = await fetch("/echo/json"); // notice the await
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
}

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


236



You are using Ajax incorrectly. The idea is not to have it return anything, but instead hand off the data to something called a callback function, which handles the data.

That is:

function handleData( responseData ) {

    // Do what you want with the data
    console.log(responseData);
}

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

Returning anything in the submit handler will not do anything. You must instead either hand off the data, or do what you want with it directly inside the success function.


187



The simplest solution is create a JavaScript function and call it for the Ajax success callback.

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

180



I will answer with a horrible-looking, hand-drawn comic. The second image is the reason why result is undefined in your code example.

enter image description here


146



Angular1

For people who are using AngularJS, can handle this situation using Promises.

Here it says,

Promises can be used to unnest asynchronous functions and allows one to chain multiple functions together.

You can find a nice explanation here also.

Example found in docs mentioned below.

  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 and Later

In Angular2 with look at the following example, but its recommended to use Observables with Angular2.

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

}

You can consume that in this way,

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

See the original post here. But Typescript does not support native es6 Promises, if you want to use it, you might need plugin for that.

Additionally here is the promises spec define here.


106