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

javascript jquery ajax asynchronous


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




Answer 1 Felix Kling


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

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

Проблема

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

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

Synchronous

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

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

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

var item = findItem();

// Do something with item
doSomethingElse();

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

Asynchronous

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

Именно это и происходит,когда ты делаешь запрос на Аякс.

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

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


Solution(s)

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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


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

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

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


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

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

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

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

becomes

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

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

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

foo(myCallback);

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

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

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

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

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

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


ES2015 +: обещания с then ()

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

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

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

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

function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}

delay()
  .then(function(v) { // `delay` returns a promise
    console.log(v); // Log the value once it is resolved
  })
  .catch(function(v) {
    // Or do something else if it is rejected 
    // (it would not happen in this example, since `reject` is not called).
  });

Применительно к нашему звонку по "Аяксу" мы могли бы использовать такие обещания:

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

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

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

Больше информации об обещаниях: HTML5 - скалы - Обещания JavaScript

Боковая заметка:отложенные объекты jQuery

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

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

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

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

Боковая заметка:Обещаю получить

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

function checkPassword() {
    return $.ajax({
        url: '/password',
        data: {
            username: $('#username').val(),
            password: $('#password').val()
        },
        type: 'POST',
        dataType: 'json'
    });
}

if (checkPassword()) {
    // Tell the user they're logged in
}

Этот код неправильно понимает вышеуказанные проблемы асинхронности. В частности, $.ajax() не замораживает код, пока проверяет страницу «/ пароль» на вашем сервере - он отправляет запрос на сервер и, ожидая, немедленно возвращает объект jQuery Ajax Deferred, а не ответ с сервера. Это означает, что оператор if будет всегда получать этот отложенный объект, обрабатывать его как true и продолжать, как если бы пользователь вошел в систему. Не хорошо.

Но исправить это легко:

checkPassword()
.done(function(r) {
    if (r) {
        // Tell the user they're logged in
    } else {
        // Tell the user their password was bad
    }
})
.fail(function(x) {
    // Tell the user something bad happened
});

Не рекомендуется:Синхронные "Аякс" звонки

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

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




Answer 2 Benjamin Gruenbaum


Если вы не используете JQuery в своем коде, этот ответ для вас

Твой код должен быть чем-то вроде этого:

function foo() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
    return httpRequest.responseText;
}

var result = foo(); // always ends up being 'undefined'

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

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


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

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

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

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

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

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

(Fiddle)

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

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

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

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

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

Это было бы полезно:

getFive(onComplete);

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

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

Есть два способа решить эту проблему:

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

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

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

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

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

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

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

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

2.Код структуры

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

So:

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

Becomes:

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

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

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

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

Теперь,давайте определим,что сам Фу должен действовать соответствующим образом.

function foo(callback) {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onload = function(){ // when the request is loaded
       callback(httpRequest.responseText);// we're calling our method
    };
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
}

(fiddle)

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

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




Answer 3 cocco


XMLHttpRequest 2 (прежде всего прочитайте ответы Бенджамина Грюнбаума и Феликса Клинга )

Если вы не используете jQuery и хотите красивый короткий XMLHttpRequest 2,который работает как на современных браузерах,так и на мобильных,я предлагаю использовать его таким образом:

function ajax(a, b, c){ // URL, callback, just a placeholder
  c = new XMLHttpRequest;
  c.open('GET', a);
  c.onload = b;
  c.send()
}

Как видите:

  1. Это короче,чем все остальные функции,перечисленные в списке.
  2. Обратный вызов устанавливается напрямую (поэтому нет лишних лишних замыканий).
  3. Он использует новую загрузку (так что вам не нужно проверять состояние готовности &&)
  4. Есть некоторые другие ситуации,которые я не помню,которые делают XMLHttpRequest 1 раздражающим.

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

Самый простой:

this.response

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

e.target.response

Example:

function callback(e){
  console.log(this.response);
}
ajax('URL', callback);

Или (вышеприведенная лучше анонимные функции всегда являются проблемой):

ajax('URL', function(e){console.log(this.response)});

Нет ничего проще.

Теперь некоторые наверняка скажут,что лучше использовать onreadystatechange или даже имя переменной XMLHttpRequest.Это неправильно.

Ознакомьтесь с расширенными функциями XMLHttpRequest

Поддерживаются все *современные браузеры.И я могу подтвердить,что использую этот подход,так как XMLHttpRequest 2 существует.У меня никогда не было проблем со всеми браузерами,которые я использую.

onreadystatechange полезен только в том случае,если вы хотите получить заголовки в состоянии 2.

Использование имени переменной XMLHttpRequest является еще одной большой ошибкой, поскольку вам нужно выполнить обратный вызов внутри замыканий onload / oreadystatechange, иначе вы его потеряли.


Теперь,если вы хотите что-то более сложное,используя post и FormData,вы можете легко расширить эту функцию:

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

Опять же ... это очень короткая функция, но она получает и публикует.

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

x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data

Или передайте полный элемент формы ( document.getElementsByTagName('form')[0] ):

var fd = new FormData(form);
x(url, callback, 'post', fd);

Или установите некоторые пользовательские значения:

var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

Существует только this.type , который имеет значение error.

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

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

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


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

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

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

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

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

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

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

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

Только если у вас есть страница,на которой вы загружаете всегда один и тот же XMLJSON или что-то еще,что вам нужно только одна функция.В этом случае слегка модифицируйте функцию Ajax и замените b на вашу специальную функцию.


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

Если вы хотите ВЫДВИЖИТЬ функцию...

Да,ты можешь.

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

Но с XMLHttpRequest 2 можно много чего сделать:

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

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




Answer 4 Benjamin Gruenbaum


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

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

Твой код должен быть чем-то вроде этого:

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

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

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


Основной вопрос

Модель параллелизма 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, которые их возвращают . Например, более современное fetch замены AJAX или $.get возврата $ .get jQuery.

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

С обещаниями

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

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

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

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 развернуть . Это как коробка, с которой ты можешь открыть.

Применяя это

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

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

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

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

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

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

ES2016 (ES7)

В ES7 это еще более стандартизировано, сейчас есть несколько предложений, но во всех из них вы можете 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
}

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




Answer 5 Nic


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

То есть:

function handleData( responseData ) {

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

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

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