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

У меня есть функция foo, которая выполняет асинхронный запрос. Как мне вернуть ответ / результат от foo?

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

Пример использования функции jQuery ajax:

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            result = response;
            // return response; // <- I tried that one as well
        }
    });

    return result; // It always returns `undefined`
}

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

function foo() {
    var result;

    fs.readFile("path/to/file", function(err, data) {
        result = data;
        // return data; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}

Пример использования блока then обещания:

function foo() {
    var result;

    fetch(url).then(function(response) {
        result = response;
        // return response; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}

person Felix Kling    schedule 08.01.2013    source источник


Ответы (43)


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

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

Эта проблема

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

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


Решение (я)

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

Вы спросите, почему это плохо?

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

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

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

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

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


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

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

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

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

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

Вот пример, описывающий функцию delay findItem() выше:

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

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


Разрешить функциям принимать обратные вызовы

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

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

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

становится

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

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

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

foo(myCallback);

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

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

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

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

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

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


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

Promise API - это новая функция ECMAScript. 6 (ES2015), но уже имеет хорошую поддержку браузера. Также существует множество библиотек, которые реализуют стандартный 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).
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

Применительно к нашему вызову 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("https://jsonplaceholder.typicode.com/todos/1")
  .then(function(result) {
    console.log(result); // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

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

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

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

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

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

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

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

Боковое примечание: ошибки с обещаниями

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

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

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

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

Но исправить это просто:

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

Не рекомендуется: синхронные вызовы Ajax.

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

Без jQuery

Если вы напрямую используете объект XMLHttpRequest, передайте false в качестве третьего аргумента в _ 51_.

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

person Felix Kling    schedule 08.01.2013
comment
@Pommy: Если вы хотите использовать jQuery, вы должны включить его. См. docs.jquery.com/Tutorials:Getting_Started_with_jQuery. - person Felix Kling; 17.01.2013
comment
В решении 1, sub jQuery, я не мог понять эту строку: If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax. (Да, я понимаю, что мой ник в этом случае немного ироничен) - person cssyphus; 07.02.2013
comment
@gibberish: Ммм, я не знаю, как это сделать яснее. Вы видите, как вызывается foo и передается ему функция (foo(function(result) {....});)? result используется внутри этой функции и является ответом на запрос Ajax. Для ссылки на эту функцию первый параметр foo называется callback и присваивается success вместо анонимной функции. Итак, $.ajax вызовет callback, когда запрос будет успешным. Я попытался объяснить это немного подробнее. - person Felix Kling; 07.02.2013
comment
Чат для этого вопроса мертв, поэтому я не уверен, где предлагать изложенные изменения, но я предлагаю: 1) Измените синхронную часть на простое обсуждение того, почему это плохо, без примера кода, как это сделать. 2) Удалите / объедините примеры обратного вызова, чтобы показать только более гибкий отложенный подход, который, я думаю, может быть немного проще для тех, кто изучает Javascript. - person Chris Moschini; 16.04.2013
comment
Всего лишь один небольшой комментарий к синхронным запросам ... если вы реализуете что-то вроде интерфейса, совместимого с SCORM, вы должны кэшировать все значения и немедленно возвращать их для большинства запросов, но вы должны синхронно выполнять команды сброса и выхода, чтобы намеренно заблокировать закрытие канала. SCORM был довольно ужасным интерфейсом для Интернета, и это был единственный случай, когда вам нужно было выполнять синхронные вызовы. - person Tracker1; 19.02.2015
comment
@johnny: Да. Вот почему код, которому необходим доступ к ответу, должен находиться внутри обратного вызова, а не после вызова функции (как показано в самом первом примере функции «Разрешить» принимать обратные вызовы). - person Felix Kling; 30.09.2015
comment
Я должен похвалить этот самый эпический из ответов. Я хочу упомянуть только одно. Я считаю, что предложение просто не использовать $ .getJSON кажется второстепенным шагом в этом вопросе. Что, если simple хочет проверить результат успешного выполнения $ .getJSON и проверить наличие чего-либо внутри этого объекта? (Моя текущая ситуация) ... stackoverflow.com/questions/33008396/ - person Jessi; 08.10.2015
comment
@Jessi: Думаю, вы неправильно поняли эту часть ответа. Вы не можете использовать $.getJSON, если хотите, чтобы запрос Ajax был синхронным. Однако вам не следует, чтобы запрос был синхронным, поэтому это не применимо. Вы должны использовать обратные вызовы или обещания для обработки ответа, как это объясняется ранее в ответе. - person Felix Kling; 08.10.2015
comment
Я использую отложенные объекты jquery .done и .fail, это нормально? Я действительно могу видеть объект внутри моего теста через console.dir (результат). В настоящее время я работаю над тем, чтобы выяснить, как проверить ценность чего-либо внутри объекта. Спасибо за терпение, я новичок в этом. - person Jessi; 08.10.2015
comment
Это хорошо объясняется, но я все еще не могу заставить свою работать, и это очень просто. Причина, по которой я этого не понимаю, состоит в том, что в примере foo () функции ничего не передается. В моем случае у меня есть foo (var1, var2, var3), так как я могу вызвать foo (function (result) {// выполнение ... с var1, var2, var3 - person MadeInDreams; 19.01.2016
comment
использовать обратные вызовы проще, если вы хотите протестировать свой код :) - person RicardoE; 08.09.2016
comment
@RicardoE: с чего вы так думаете? Многие среды тестирования поддерживают обещания. - person Felix Kling; 08.09.2016
comment
Это было одно из лучших объяснений того, как работают обещания и асинхронный javascript. Спасибо, что нашли время поделиться этим. - person Travis Michael Heller; 09.09.2016
comment
@FelixKling Я не говорил, что он не поддерживается ... Я сказал, что это проще :) Конечно, поддерживается! - person RicardoE; 09.09.2016
comment
Ваша ajax() Promise реализация во многом похожа на fetch API. - person Patrick Roberts; 19.09.2016
comment
Уведомление об устаревании: обратные вызовы jqXHR.success (), jqXHR.error () и jqXHR.complete () удалены с jQuery 3.0. Вместо этого вы можете использовать jqXHR.done (), jqXHR.fail () и jqXHR.always (). - person Jo Smo; 27.09.2016
comment
Небольшое пояснение - Dojo реализовал Deferred до jQuery. Таким образом, концепция Deferred не является эксклюзивной и не создается jQuery. - person Manachi; 05.10.2016
comment
Вы использовали callback и myCallback. Если я прав, они одинаковы, и вы имеете в виду success: myCallback(response)? - person Shaiju T; 31.10.2016
comment
@stom: оба одинаковые Они ссылаются на одно и то же значение в примере. Вы имеете в виду success: myCallback(response) Нет. Я действительно имею в виду success: callback. callback - это имя параметра foo, а значение параметра присваивается success. myCallback - это имя функции, переданной в foo, но это не имеет значения. Рассмотрим этот упрощенный пример: var x = 42; function foo(y) { console.log(y); }; foo(x);. y является параметром foo, поэтому используется console.log(y). Мы могли сделать console.log(x);, но это противоречит цели функции. - person Felix Kling; 31.10.2016
comment
Спасибо за ответ, это означает, что когда ответ приходит от сервера, при успешном выполнении будет присвоено значение параметру foo's callback, который, в свою очередь, вызовет foo(myCallback) и передаст значение параметру результата myCallback? - person Shaiju T; 01.11.2016
comment
@stom: foo(myCallback) уже был вызван. Это то, что инициирует вызов Ajax. Но да, когда ответ будет получен, jQuery вызовет функцию, назначенную success, и передаст ей ответ. - person Felix Kling; 01.11.2016
comment
@MadaraUchiha, я категорически не согласен с вашим недавним изменением (большой TL; DR: Use Promises). Этот вопрос является дублирующей целью для большого количества общих вопросов, связанных с асинхронностью (как вернуть переменную). Размещение этого кода / информации в начале сбивает с толку людей, которым мы говорим, что это дубликат их проблемы. Код, который вы предоставили, показывает только, как использовать Promise, который автоматически возвращается jQuery $.ajax(), когда вы не предоставляете обратный вызов. Вы изменили ответ общего назначения на ответ, который теперь относится только к $.ajax() или функциям, которые автоматически возвращают обещание. (Продолжение) - person Makyen♦; 17.12.2016
comment
@MadaraUchiha: (продолжение) Новички теперь считают это слишком конкретным и неприменимым к их вопросу (что может относиться к любой асинхронной функции, а не только к $.ajax()). $.ajax() используется просто как пример более общей проблемы. Обратите внимание, что я сейчас здесь только на этом вопросе / ответе, потому что то, что я описываю как ответ новичков, - это то, что произошло, когда я предложил это как цель дублирования в вопросе. Если бы вы не были модератором, я бы просто отменил правку, пока мы ее обсуждаем, но я потенциально не хочу участвовать в конкурсе отката с модератором. - person Makyen♦; 17.12.2016
comment
Я предполагаю, что с наличием BabelJS и, следовательно, поддержки ES7, этот ответ заслуживает обновления, чтобы иметь возможность фактически вернуться к синхронному стилю с использованием асинхронных функций, применяя комбинации async / await. - person jAndy; 22.12.2016
comment
@jAndy: async/await не является частью ES7 (ES2016), но будет частью ES2017. Так что да, я согласен, что поговорить об этом было бы полезно. Однако понимать обещания еще нужно. Я посмотрю, смогу ли что-нибудь придумать в ближайшие несколько дней (и немного реструктурирую ответ, чтобы он не был слишком загроможден). - person Felix Kling; 22.12.2016
comment
Я думаю, что этот ответ может быть связан с обновлением, поскольку ES8 отсутствует - person DarkMukke; 07.09.2017
comment
@DarkMukke: он уже упоминает async / await. Как вы думаете, чего еще не хватает? - person Felix Kling; 07.09.2017
comment
@FelixKling, извините, вы неправильно поняли, я думаю, что пост идеален, но callback popular in node, черт возьми, все пытаются отказаться от обратных звонков как можно скорее в пользу обещаний. Я просто чувствую, что это утверждение устарело, и я подумал, что если 1 бит, некоторые другие биты тоже могут - person DarkMukke; 07.09.2017
comment
Можете ли вы также сделать пустой .then (), чтобы дождаться завершения асинхронной функции? - person Gobliins; 15.11.2017
comment
@Gobliins: Вы можете добавить .then(), но это не имеет значения. Результат будет такой же, как без добавления .then(). - person Felix Kling; 15.11.2017
comment
Видите ли, у меня есть мангуст, который должен подключаться к mongodb внутри блока mocha before перед выполнением тестов. Поэтому мне нужно дождаться установления соединения, прежде чем можно будет начать тестирование. - person Gobliins; 16.11.2017
comment
@FelixKling, чтобы ответ был доступен только в функции myCallback, и, следовательно, код, в котором я хочу что-то сделать с результатом, должен быть помещен сюда правильно? или я что-то упускаю? Вне обратного вызова значение результата будет неопределенным, даже если myCallback вернет результат? - person loki; 29.12.2017
comment
Разве это не должно рекламироваться как ES1017 +: Promises с async / await / тогда? Как вы определенно называете это в следующем решении, но все еще используете его там для получения окончательного результата? - person Ben; 04.01.2018
comment
AirAsia, крупнейший оператор LCC в Азии, по-прежнему использует Sync jQuery ajax на своем веб-сайте, поэтому определенно используется за пределами SCORM. Меня так тошнило от зависания моего браузера (из-за столь же невыразительного бэкенда), что я решил погрузиться в код, а затем обнаружил ужас async: false. Невероятно, но они выиграли множество туристических наград за свою медленную работу, сбои браузера, блокировку VPN и выдачу исключений. - person Mâtt Frëëman; 13.03.2018
comment
jQuery deferred предоставляет реализацию обещания. Который может иметь свои причуды до такой степени, что он достаточно непривлекателен, чтобы использовать его вместо обещаний ES6. Тем не менее, это возможно, можно делать Promise.resolve($.ajax(...).promise()) или await $.ajax(...).promise() вместо работы с jQuery done и fail. - person Estus Flask; 01.02.2019
comment
Если мы просто используем разрешение, а не отклонение, как в вашем примере async / await, нам не нужно добавлять отклонение в качестве параметра функции, верно? - person baptx; 18.01.2020
comment
Это спасло меня после 10 часов сидения, чтобы узнать, почему ничего не было возвращено или не было возвращено undefined, как бы я ни крутил код, спасибо :) - person GeniusGeek; 30.04.2020
comment
Теперь вы также можете использовать шаблоны async / await с обратными вызовами вместо обещаний: github.com/bessiambre/casync - person Benoit Essiambre; 25.05.2020
comment
Извините, но меня просто похоронили. На этот вопрос больше 100 000 слов ответов. Мне просто нужен простой: если я function(url).then( response => console.log(response) ) помещаю допустимый текст в консоль, как мне тогда return этот действительный текст вернуться к тому, что вызывает функцию? - person dafydd; 18.03.2021
comment
@dafyd: вы не можете просто вернуть сам текст, потому что он недоступен в момент возврата функции. Вы можете только вернуть обещание, и тогда вызывающий должен сделать то же самое, что и вы в своем примере: return function(url); и outerFunction().then(response => ...). - person Felix Kling; 18.03.2021
comment
в примере части ES2017 + код return await superagent.get(...) внутри async function getAllBooks(): кажется, что await там не требуется. Это правильно? - person S.Serpooshan; 23.07.2021
comment
@ S.Serpooshan: Да, вы правы, в этом нет необходимости. - person Felix Kling; 23.07.2021

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

Ваш код должен выглядеть примерно так:

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

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

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

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


С чем вы столкнулись

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

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

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

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

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

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

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

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

  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

Становится:

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

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

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

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

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

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

(скрипка)

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

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

person Benjamin Gruenbaum    schedule 29.05.2013
comment
синхронные запросы блокируют выполнение кода и могут вызывать утечку памяти и событий. Как синхронный запрос может утечь память? - person Matthew G; 16.08.2013
comment
@MatthewG Я назначил награду за него в этом вопросе, Посмотрю, что я выловлю. А пока удаляю цитату из ответа. - person Benjamin Gruenbaum; 16.08.2013
comment
Для справки: XHR 2 позволяет нам использовать обработчик onload, который срабатывает только тогда, когда readyState равно 4. Конечно, в IE8 это не поддерживается. (iirc, может потребоваться подтверждение.) - person Florian Margaine; 23.12.2013
comment
Ваше объяснение того, как передать анонимную функцию в качестве обратного вызова, действительно, но вводит в заблуждение. Пример var bar = foo (); запрашивает определение переменной, а предложенный вами foo (functim () {}); не определяет бар - person scrowler; 07.08.2014
comment
Я понимаю, почему это может вводить в заблуждение, но после нескольких месяцев отсутствия ответа на этот вопрос я наконец понимаю, что происходит и как этого избежать (вы просто хотите, чтобы ваши асинхронные функции не использовали какие-либо общедоступные переменные и передавали их данные возвращаются функциям с их собственными частными переменными). Понятия не имею, почему этот ответ привел меня к этому / моему решению, но это определенно произошло. Спасибо чувак! Я также понимаю, что этот вопрос может не совсем соответствовать тому, что я описываю, но он, по крайней мере, связан / похож. - person Netside; 13.09.2020

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

Простейший:

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

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

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

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

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

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

Или установите несколько пользовательских значений:

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

Есть только this.type, для которого установлено значение error.

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


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

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

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

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

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

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

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

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

person cocco    schedule 19.08.2013
comment
Хотя этот ответ хорош (и мы все любим XHR2, а публикация файловых данных и составных данных - это просто потрясающе) - он показывает синтаксический сахар для публикации XHR с помощью JavaScript - вы можете поместить это в сообщение в блоге (Хотелось бы) или даже в библиотеке (не уверен, что имя x, ajax или xhr может быть лучше :)). Я не понимаю, как он отвечает на возврат ответа от вызова AJAX. (кто-то мог еще сделать var res = x("url") и не понять, почему не работает;)). Кстати, было бы здорово, если бы вы вернули c из метода, чтобы пользователи могли подключиться к error и т. Д. - person Benjamin Gruenbaum; 23.08.2013
comment
2.ajax is meant to be async.. so NO var res=x('url').. В этом весь смысл этого вопроса и ответов :) - person Benjamin Gruenbaum; 23.08.2013
comment
почему в функциях есть параметр 'c', если в первой строке вы перезаписываете то значение, которое у него было? я что-то упускаю? - person Brian H.; 21.12.2016
comment
Вы можете использовать параметры в качестве заполнителя, чтобы не писать несколько раз var - person cocco; 21.12.2016
comment
@cocco Значит, вы написали вводящий в заблуждение, нечитаемый код в SO ответе, чтобы сэкономить несколько нажатий клавиш? Пожалуйста, не делай этого. - person stone; 08.10.2017
comment
@cocco Мне намного проще называть переменные реальными словами, чем использовать a, b, d, e, c и держать под рукой заметки, объясняющие буквы этими словами. Кроме того, я обнаружил, что неиспользуемый параметр очень сбивает с толку - сначала мне было интересно, возвращается ли результат каким-либо образом через параметр c. Я не вижу, как сохранение нескольких нажатий клавиш const или let повышает удобочитаемость. Я бы подумал, что если вы пойдете на такие крайности, чтобы «избежать записи несколько раз var», вы могли бы просто написать его один раз и перечислить все переменные в одном объявлении. - person Sebi; 10.01.2019
comment
пожалуйста, обновите свой ответ, указав читаемые имена аргументов / переменных и удалите комментарии, которые вы используете, чтобы исправить неправильное именование - person YakovL; 15.06.2019
comment
Пожалуйста, используйте более информативные имена параметров - person user4617883; 14.12.2019

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

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

Ваш код должен выглядеть примерно так:

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

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

Краткий обзор обещаний

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

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

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

Когда мы вызываем .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, and we can't access the plain value
getFive().then(function(five){
   document.body.innerHTML = five;
});

По сути, вместо того, чтобы возвращать значение, чего мы не можем сделать из-за модели параллелизма - мы возвращаем оболочку для значения, которое мы можем развернуть < / em> с 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, Nodes.js, and io.js 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
    // The 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
   // The server call is done here, and the 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 are 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
}

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

person Benjamin Gruenbaum    schedule 12.05.2015
comment
Это должен быть принятый ответ. +1 для async / await (хотя не return await data.json();?) - person Lewis Donovan; 13.11.2019
comment
Такое прекрасное объяснение .. Спасибо, сэр - person beyondtdr; 05.12.2020

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

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

person Nic    schedule 23.05.2014
comment
Этот ответ полностью семантический ... ваш метод успеха - это просто обратный вызов в обратном вызове. Вы можете просто взять success: handleData, и это сработает. - person Jacques ジャック; 04.01.2016
comment
А что, если вы хотите вернуть responseData вне handleData ... :) ... как вы это сделаете ...? ... потому что простой return вернет его к успешному обратному вызову ajax ... а не за пределами handleData ... - person pesho hristov; 19.02.2016
comment
@Jacques & @pesho hristov Вы упустили этот момент. Обработчик отправки - это не метод success, это область действия $.ajax. - person travnik; 28.05.2018
comment
@travnik Я этого не пропустил. Если вы возьмете содержимое handleData и поместите его в метод успеха, он будет действовать точно так же ... - person Jacques ジャック; 13.06.2018
comment
Этот ответ НАСТОЛЬКО ясен и прост и сэкономил мне (часы!). Спасибо. Если бы мог, проголосовал бы 100 раз. - person S3DEV; 24.06.2020

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

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

person Johannes Fahrenkrug    schedule 11.08.2016
comment
Картинка стоит тысячи слов, Человек А - спросить у человека Б детали, чтобы починить его машину, в свою очередь Человек Б - выполняет вызов Ajax и ожидает ответа от сервера для деталей ремонта автомобиля, когда ответ получен, функция Ajax Success вызывает функцию Person B и передает ей ответ в качестве аргумента, Person A получает ответ. - person Shaiju T; 31.10.2016
comment
Было бы здорово, если бы вы добавляли строки кода к каждому изображению, чтобы проиллюстрировать концепции. - person Hassan Baig; 05.02.2018
comment
Тем временем парень с машиной застрял на обочине дороги. Он требует, чтобы машина была отремонтирована, прежде чем продолжить. Теперь он один на обочине дороги и ждет ... Он предпочел бы разговаривать по телефону, ожидая изменения статуса, но механик не стал бы этого делать ... Механик сказал, что он должен продолжать свою работу и не может просто поговорите по телефону. Механик пообещал, что перезвонит ему, как только сможет. Примерно через 4 часа парень сдается и звонит в Uber. - Пример тайм-аута. - person barrypicker; 23.10.2019
comment
@barrypicker :-D Великолепно! - person Johannes Fahrenkrug; 23.10.2019
comment
Ха-ха, отличная работа, @JohannesFahrenkrug! Это будет очень полезно тренерам, которые пытаются обучить асинхронному программированию новичков. - person Prime; 05.03.2021
comment
Но с функциями обратного вызова я чувствую, что человека слева в последнем кадре заставляют не давать другому человеку свой номер телефона. Вместо этого они должны сказать другому человеку: «Вот все, что я хочу сделать с информацией от чувака по телефону». Делай все это и никогда не говори мне. Что мне не хватает? - person Fing Lixon; 10.04.2021
comment
@FingLixon Это не идеальный комикс :-D. Второе изображение должно иллюстрировать, что происходит, когда вы пытаетесь прочитать значение слишком рано (до того, как произойдет обратный вызов). Третье изображение иллюстрирует настройку метода обратного вызова: парень слева в основном является обработчиком обратного вызова: он будет вызван с информацией, как только она станет доступной, и затем сможет делать с ней все, что захочет. Теперь я думаю, что было плохой идеей иметь ДВА телефонных звонка в этом комиксе: звонок в магазин и звонок парню слева. Я должен был упростить это, извините за это. - person Johannes Fahrenkrug; 12.04.2021
comment
[пожимает плечами] Спасибо. Мне просто нужно больше практики, чтобы по-настоящему понять функции обратного вызова. - person Fing Lixon; 13.04.2021
comment
Такие прекрасные рисунки - person Daisy the cat; 23.04.2021

Самое простое решение - создать функцию 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 a 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);
});
person Hemant Bavle    schedule 18.02.2014
comment
Я не знаю, кто проголосовал против. Но это работа, которая сработала, на самом деле я использовал этот подход для создания целого приложения. Jquery.ajax не возвращает данные, поэтому лучше использовать вышеуказанный подход. Если это не так, пожалуйста, объясните и предложите лучший способ сделать это. - person Hemant Bavle; 28.03.2014
comment
Извините, я забыл оставить комментарий (обычно так и делаю!). Я проголосовал против. Голоса против не указывают на фактическую правильность или отсутствие, они указывают на полезность в контексте или отсутствие. Я не считаю ваш ответ полезным, учитывая Феликс, который уже объясняет это только более подробно. Кстати, зачем вам строчить ответ, если это JSON? - person Benjamin Gruenbaum; 10.04.2014
comment
хорошо .. @Benjamin Я использовал stringify, чтобы преобразовать объект JSON в строку. И спасибо, что разъяснили вашу точку зрения. Буду иметь в виду публиковать более подробные ответы. - person Hemant Bavle; 10.04.2014
comment
А что, если вы хотите вернуть responseObj вне successCallback ... :) ... как вы это сделаете ...? ... потому что простой return вернет его к обратному вызову успеха ajax ... а не за пределами successCallback ... - person pesho hristov; 19.02.2016

Угловой 1

Люди, использующие AngularJS, могут справиться с этой ситуацией с помощью обещаний.

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

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

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

Пример из упомянутой ниже документации.

  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.

Angular 2 и новее

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

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

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

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

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

Кроме того, здесь представлена ​​спецификация обещаний.

person Maleen Abewardana    schedule 26.08.2014
comment
Однако это не объясняет, как обещания решат эту проблему. - person Benjamin Gruenbaum; 04.11.2014
comment
Оба метода jQuery и fetch также возвращают обещания. Я предлагаю пересмотреть ваш ответ. Хотя jQuery не совсем то же самое (тогда есть, но уловка нет). - person Tracker1; 19.02.2015

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

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

Пример:

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

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

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

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

Параллельный

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

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

Пример:

var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", JSON.stringify(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 в той же позиции, что и запись, к которой он относится, даже если результаты приходят не по порядку (поскольку асинхронные вызовы не обязательно завершаются в том порядке, в котором они были запущены ).

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

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:", JSON.stringify(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:", JSON.stringify(results));
});

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

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

Если doSomethingAsync дает вам обещание, вы можете используйте Promise.all:

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

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

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

Пример:

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

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

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

Серии

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

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

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

Пример:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", JSON.stringify(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), вы можете использовать _ 41_ функция с _ 42_ и _ 43_:

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:", JSON.stringify(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 (это сложнее, чем обычное сокращение 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:", JSON.stringify(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:", JSON.stringify(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; }

person T.J. Crowder    schedule 03.05.2017
comment
Не могли бы вы объяснить, как работает if (--expecting === 0) часть кода? Версия вашего решения с обратным вызовом отлично работает для меня, я просто не понимаю, как с помощью этого оператора вы проверяете количество завершенных ответов. Цените это просто отсутствие знаний с моей стороны. Есть ли альтернативный способ написания чека? - person Sarah; 28.05.2017
comment
@Sarah: expecting начинается со значения array.length, которое указывает количество запросов, которые мы собираемся сделать. Мы знаем, что обратный вызов не будет вызван, пока не будут запущены все эти запросы. В обратном вызове if (--expecting === 0) выполняет следующее: 1. Уменьшает expecting (мы получили ответ, поэтому мы ожидаем на один ответ меньше), и если значение после, уменьшение равно 0 (мы не ожидая еще каких-либо ответов), готово! - person T.J. Crowder; 28.05.2017
comment
@Henke - Я думаю, что это действительно личное предпочтение, и хотя обычно я бы предпочел регистрировать необработанные данные и позволять консоли обрабатывать их, в этом конкретном случае я думаю, что вы правы в отношении изменения. Спасибо! :-) - person T.J. Crowder; 06.05.2021

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

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/

Способ ES6 (асинхронный - ожидание)

(function(){
  async function getJoke(){
    let response = await fetch('http://api.icndb.com/jokes/random');
    let data = await response.json();
    return data.value;
  }

  getJoke().then((joke) => {
    console.log(joke);
  });
})();
person Francisco Carmona    schedule 02.06.2016

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

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

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

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

Например, в вашем случае, когда вы используете 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(); // The call happens here
});

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

person Alireza    schedule 24.05.2017
comment
Это нормально в глобальной области, но в некотором контексте модуля вы, вероятно, захотите обеспечить правильный контекст для обратного вызова, например. $.ajax({url: "api/data", success: fooDone.bind(this)}); - person steve.sims; 24.07.2017
comment
На самом деле это неверно, поскольку React - это односторонняя привязка данных. - person Matthew Brent; 04.05.2018
comment
@MatthewBrent, вы не ошибаетесь, но и не правы, реквизиты React являются объектами, и если они изменены, они меняются во всем приложении, но разработчик React не рекомендует его использовать ... - person Alireza; 14.05.2018

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

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

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

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

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

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

ОБНОВЛЕНИЕ (обещает с async / await)

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

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

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

версия async / await:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        console.error(err);
     }
  }
person Anish K.    schedule 31.10.2017
comment
считается ли это лучшим способом вернуть значение из обещания или async / await? - person edwardsmarkf; 26.09.2018
comment
@edwardsmarkf Лично я не думаю, что есть лучший способ как таковой. Я использую обещания с then / catch, async / await, а также генераторы для асинхронных частей моего кода. Это во многом зависит от контекста использования. - person Anish K.; 03.10.2018

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

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

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

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

person jsbisht    schedule 02.09.2015
comment
Здесь нет ничего особенного в использовании объекта. Это также сработало бы, если бы вы назначили ответ напрямую result. Это работает, потому что вы читаете переменную после завершения асинхронной функции. - person Felix Kling; 02.09.2015

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

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

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

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

async1(name, async2)

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

Fibers помогает в решении проблемы.

var Fiber = require('fibers')

function async1(container) {
  var current = Fiber.current
  var result
  doSomething(function(name) {
    result = name
    fiber.run()
  })
  Fiber.yield()
  return result
}

Fiber(function() {
  var name
  if (!name) {
    name = async1()
  }
  async2(name)
  // Make any number of async calls from here
}

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

person rohithpr    schedule 25.01.2016
comment
@recurf - Это не мой проект. Вы можете попробовать использовать их средство отслеживания проблем. - person rohithpr; 20.01.2017
comment
это похоже на функции генератора? developer.mozilla.org/en-US/docs/ Интернет / JavaScript / Справочник / * - person Emanegux; 07.06.2017
comment
Это все еще актуально? - person Aluan Haddad; 18.03.2018
comment
Вы можете использовать async-await, если используете некоторые из новейших версий node. Если кто-то застрял на более старых версиях, они могут использовать этот метод. - person rohithpr; 20.03.2018

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

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

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

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

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

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

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

ПРИМЕЧАНИЕ. Последние search API Spotify потребуют указания токена доступа в заголовках запроса:

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

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

person loretoparisi    schedule 12.04.2016

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

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

$.ajax({
    url: "...",
    success: callback
});
person Pablo Matias Gomez    schedule 22.04.2016

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

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

  1. Цикл событий

  2. Веб-API

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

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

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

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

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

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

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

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

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

Мы передаем нашу функцию (функцию, использующую данные, возвращаемые сервером) функции, вызывающей сервер.

Обратный звонок

function doAjax(callbackFunc, method, url) {
    var xmlHttpReq = new XMLHttpRequest();
    xmlHttpReq.open(method, url);
    xmlHttpReq.onreadystatechange = function() {

        if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
            callbackFunc(xmlHttpReq.responseText);
        }
    }
    xmlHttpReq.send(null);
}

В моем коде это называется:

function loadMyJson(categoryValue){
    if(categoryValue === "veg")
        doAjax(print, "GET", "http://localhost:3004/vegetables");
    else if(categoryValue === "fruits")
        doAjax(print, "GET", "http://localhost:3004/fruits");
    else
      console.log("Data not found");
}

обратный вызов JavaScript.info

person Aniket Jha    schedule 03.02.2018
comment
хорошо объяснено. также добавьте информацию, связанную с обещанием - person pathe.kiran; 02.05.2021

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

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

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

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

(async function(){

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

})()

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

person mikemaccana    schedule 02.06.2017
comment
К сожалению, это работает только с функциями, возвращающими обещания - например, это не работает с API Node.js, который использует обратные вызовы. И я бы не рекомендовал использовать его без Babel, потому что не все используют современные браузеры. - person Michał Perłakowski; 08.06.2017
comment
Узел 8 @ MichałPerłakowski включает nodejs.org/api/util.html#util_util_promisify_original, который использоваться для того, чтобы API node.js возвращал обещания. Есть ли у вас время и деньги для поддержки устаревших браузеров, очевидно, зависит от вашей ситуации. - person mikemaccana; 09.06.2017
comment
IE 11 по-прежнему является текущим браузером 2018 года, к сожалению, он не поддерживает await/async - person Juan Mendes; 04.10.2018
comment
IE11 не является текущим браузером. Он был выпущен 5 лет назад, по данным caniuse, его доля на мировом рынке составляет 2,5%, и если кто-то не удвоит ваш бюджет, игнорируя все современные технологии, то это не стоит времени большинства людей. - person mikemaccana; 04.10.2018

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

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

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

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

function synchronousCode() {

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

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

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

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

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

Шаг 2. Включите синхронную логику:

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

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

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

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

Другие примеры можно найти здесь.

person amaksr    schedule 27.05.2017
comment
Это интересно. Мне нравится, как он позволяет кодировать асинхронные вызовы так, как если бы вы это делали на других языках. Но технически это не настоящий JavaScript? - person J Morris; 17.06.2017

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

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

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

Чтобы запустить приведенный выше код, сделайте следующее:

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

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

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

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
person James    schedule 17.02.2018
comment
Считаете ли вы генераторы / асинхронные генераторы только решением асинхронного API? Или вы использовали бы генераторы для обертывания другого асинхронного API, такого как обещание / отложенный? Я согласен, что это еще одно сильное дополнение к асинхронной вселенной, но до сих пор не нашел правильного использования генераторов, которое заставило бы меня принять их. - person Eva Cohen; 17.01.2021

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

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

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

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

Поскольку JavaScript не имеет возможности узнать, что ему нужно дождаться завершения order_milk, прежде чем он выполнит put_in_coffee. Другими словами, он не знает, что order_milk является асинхронным - что-то, что не приведет к появлению молока до некоторого времени. JavaScript и другие декларативные языки выполняют одну инструкцию за другой, не дожидаясь.

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

order_milk(put_in_coffee);

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

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

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

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

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

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

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

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

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

В случае с молоком и кофе мы разрабатываем order_milk, чтобы возвращать обещание о прибытии молока, а затем указываем put_in_coffee как then действие, как показано ниже:

order_milk() . then(put_in_coffee)

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

order_milk() . then(put_in_coffee) . then(drink_coffee)

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

function get_data() {
  return $.ajax('/foo.json');
}

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

get_data() . then(do_something)

например,

get_data() .
  then(function(data) { console.log(data); });

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

get_data() .
  then(data => console.log(data));

Ключевое слово async

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

a();
b();

но если a асинхронный, с обещаниями мы должны написать

a() . then(b);

Выше мы говорили, что JavaScript не может знать, что ему нужно дождаться завершения первого вызова, прежде чем он выполнит второй. Было бы неплохо, если бы был способ сообщить об этом JavaScript? Оказывается, есть ключевое слово await, используемое внутри особого типа функции, называемой асинхронной функцией. Эта функция является частью будущей версии ECMAScript (ES), но она уже доступна в транспиляторах, таких как Babel с правильными настройками. Это позволяет нам просто написать

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

В вашем случае вы могли бы написать что-то вроде

async function foo() {
  data = await get_data();
  console.log(data);
}
person Community    schedule 23.01.2016

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

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

function foo(result) {
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;   // Store the async result
        }
    });
}

var result = { response: null };   // Object to hold the async result
foo(result);                       // Returns before the async completes

Обратите внимание, что вызов foo() по-прежнему ничего полезного не вернет. Однако результат асинхронного вызова теперь будет сохранен в result.response.

person David R Tribble    schedule 23.09.2015
comment
Хотя это работает, на самом деле это не лучше, чем присвоение глобальной переменной. - person Felix Kling; 24.09.2015

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

  1. Browser Promise
  2. Q - библиотека обещаний для JavaScript
  3. A + Promises.js
  4. jQuery отложено
  5. XMLHttpRequest API
  6. Использование концепции обратного вызова - как реализация в первом ответе

Пример: отложенная реализация jQuery для работы с несколькими запросами

var App = App || {};

App = {
    getDataFromServer: function(){

      var self = this,
                 deferred = $.Deferred(),
                 requests = [];

      requests.push($.getJSON('request/ajax/url/1'));
      requests.push($.getJSON('request/ajax/url/2'));

      $.when.apply(jQuery, requests).done(function(xhrResponse) {
        return deferred.resolve(xhrResponse.result);
      });
      return deferred;
    },

    init: function(){

        this.getDataFromServer().done(_.bind(function(resp1, resp2) {

           // Do the operations which you wanted to do when you
           // get a response from Ajax, for example, log response.
        }, this));
    }
};
App.init();

person Mohan Dere    schedule 13.08.2016
comment
Зачем включать фрагмент стека, который выдает ошибку? - person Henke; 09.05.2021

Используйте 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();
person Mahfuzur Rahman    schedule 24.04.2017

Использование обещания

Самый лучший ответ на этот вопрос - использовать Promise.

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

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

ajax("GET", "/test", "acrive=1").then(function(result) {
    // Code depending on result
})
.catch(function() {
    // An error occurred
});

Но ждать...!

Проблема с использованием обещаний!

Почему мы должны использовать наши собственные промисы?

Некоторое время я использовал это решение, пока не выяснил, что в старых браузерах есть ошибка:

Uncaught ReferenceError: Promise не определен

Поэтому я решил реализовать свой собственный класс Promise для компиляторов JavaScript от ES3 до ниже, если он не определен. Просто добавьте этот код перед основным кодом, а затем безопасно используйте Promise!

if(typeof Promise === "undefined"){
    function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) {
            throw new TypeError("Cannot call a class as a function");
        }
    }
    var Promise = function () {
        function Promise(main) {
            var _this = this;
            _classCallCheck(this, Promise);
            this.value = undefined;
            this.callbacks = [];
            var resolve = function resolve(resolveValue) {
                _this.value = resolveValue;
                _this.triggerCallbacks();
            };
            var reject = function reject(rejectValue) {
                _this.value = rejectValue;
                _this.triggerCallbacks();
            };
            main(resolve, reject);
        }
        Promise.prototype.then = function then(cb) {
            var _this2 = this;
            var next = new Promise(function (resolve) {
                _this2.callbacks.push(function (x) {
                    return resolve(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.catch = function catch_(cb) {
            var _this2 = this;
            var next = new Promise(function (reject) {
                _this2.callbacks.push(function (x) {
                    return reject(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.triggerCallbacks = function triggerCallbacks() {
            var _this3 = this;
            this.callbacks.forEach(function (cb) {
                cb(_this3.value);
            });
        };
        return Promise;
    }();
}
person Amir Fo    schedule 07.12.2018

Вопрос был такой:

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

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

Как сделать так, чтобы асинхронный код выглядел синхронным?

Решением будет избегать обратных вызовов и использовать комбинацию Promises и async / await.

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

(Хотя его можно написать на JavaScript, я предпочитаю писать его на Python и компилировать в JavaScript с помощью Transcrypt. Будет достаточно ясно.)

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

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

Определите функцию, которая возвращает Promise, в данном случае вызов Ajax:

def read(url: str):
    deferred = S.Deferred()
    S.ajax({'type': "POST", 'url': url, 'data': { },
        'success': lambda d: deferred.resolve(d),
        'error': lambda e: deferred.reject(e)
    })
    return deferred.promise()

Используйте асинхронный код, как если бы он был синхронным:

async def readALot():
    try:
        result1 = await read("url_1")
        result2 = await read("url_2")
    except Exception:
        console.warn("Reading a lot failed")
person Pieter Jan Bonestroo    schedule 13.01.2018
comment
Любой, кто заинтересован в использовании async / await, вероятно, также захочет прочитать этот ответ (и, возможно, мой комментарий под ним :-). - person Henke; 11.05.2021

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

Итак, ваш фрагмент кода можно переписать, чтобы он немного отличался:

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}
person Khoa Bui    schedule 05.07.2017
comment
В обратных вызовах или JavaScript нет ничего изначально асинхронного. - person Aluan Haddad; 18.03.2018
comment
Зачем оставлять var result; и return result;? Последний по-прежнему будет всегда возвращать undefined! - person Henke; 09.05.2021

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

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

Вам нужно знать о трех вещах; Очередь; цикл событий и стек

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

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

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

  1. вызовите foo.com/api/bar с помощью foobarFunc
  2. Пойдите, выполните бесконечный цикл ... и так далее

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

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

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

Здесь главное - порядок исполнения. Это

КОГДА что-то запускается

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

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

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

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

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

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

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

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

person Matthew Brent    schedule 04.05.2018
comment
Я изо всех сил пытаюсь принять обратные вызовы как обещания. это все равно что сказать, что мука похожа на хлеб, но это не так. вы используете муку, воду и другие ингредиенты, смешиваете их, и в конечном итоге после процесса получается хлеб. - person Eva Cohen; 17.01.2021
comment
Это правда - мне кажется, я пытался сказать что-то, что не совсем понимает то, что я имел в виду. Обещание в JS, очевидно, представляет собой нечто отличное от обратного вызова, однако при программировании любых асинхронных функций вы собираетесь выполнять обратный вызов. Обещание представляет значение, но обратный вызов - это то, что нам нужно сделать с этим значением в какой-то момент в будущем, когда оно вернется. - person Matthew Brent; 18.01.2021
comment
Обещание в основном бесполезно (но не всегда) без обратного вызова, чтобы что-то сделать с разрешенным значением - person Matthew Brent; 18.01.2021

Прочитав здесь все ответы и поделившись своим опытом, я хотел бы вернуться к деталям callback, promise and async/await для асинхронного программирования на JavaScript.

1) Обратный вызов. Основной причиной обратного вызова является выполнение кода в ответ на событие (см. пример ниже). Мы каждый раз используем обратный вызов в JavaScript.

const body = document.getElementsByTagName('body')[0];
function callback() {
  console.log('Hello');
}
body.addEventListener('click', callback);

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

asyncCallOne(function callback1() {
  asyncCallTwo(function callback2() {
    asyncCallThree(function callback3() {
        ...
    })
  })
})

2) Promise: синтаксис ES6 - Promise решает проблему ада обратного вызова!

const myFirstPromise = new Promise((resolve, reject) => {
  // We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
  // In this example, we use setTimeout(...) to simulate async code.
  // In reality, you will probably be using something like XHR request or an HTML5 API.
  setTimeout(() => {
    resolve("Success!")  // Yay! Everything went well!
  }, 250)
})

myFirstPromise
  .then((res) => {
    return res.json();
  })
  .then((data) => {
    console.log(data);
  })
  .catch((e) => {
    console.log(e);
  });

myFirstPromise - это экземпляр Promise, который представляет процесс асинхронных кодов. Функция разрешения сигнализирует о завершении работы экземпляра Promise. После этого мы можем вызвать .then () (цепочку из .then по своему усмотрению) и .catch () для экземпляра обещания:

then — Runs a callback you pass to it when the promise has fulfilled.
catch — Runs a callback you pass to it when something went wrong.

3) Async / Await: новый синтаксис ES6 - Await, по сути, является синтаксическим сахаром для Promise!

Функция Async предоставляет нам чистый и лаконичный синтаксис, который позволяет нам писать меньше кода для достижения того же результата, который мы получили бы с обещаниями. Async / Await похож на синхронный код, а синхронный код намного проще читать и писать. Чтобы отловить ошибки с помощью Async / Await, мы можем использовать блок try...catch. Здесь вам не нужно писать цепочку из .then () синтаксиса Promise.

const getExchangeRate = async () => {
  try {
    const res = await fetch('https://getExchangeRateData');
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

getExchangeRate();

Вывод: это всего три синтаксиса асинхронного программирования на JavaScript, которые вы должны хорошо понимать. Поэтому, если возможно, я рекомендую вам использовать обещание или async / await для рефакторинга ваших асинхронных кодов (в основном для запросов XHR)!

person SanjiMika    schedule 19.01.2020
comment
Привет, хотя содержание этого ответа является точным, он действительно не отвечает на вопрос OP (как вернуть что-то из асинхронного вызова?) - person Bharath Ram; 25.08.2020

Используя ES2017, вы должны иметь это как объявление функции.

async function foo() {
  var response = await $.ajax({url: '...'})
  return response;
}

И выполняем это вот так.

(async function() {
  try {
    var result = await foo()
    console.log(result)
  } catch (e) {}
})()

Или синтаксис обещания.

foo().then(response => {
  console.log(response)

}).catch(error => {
  console.log(error)

})

Фрагмент стека, демонстрирующий приведенный выше код.

// The function declaration:
async function foo() {
  var response = await $.ajax({
    url: 'https://jsonplaceholder.typicode.com/todos/1'
  })
  return response;
}

// Execute it like this:
(async function() {
  try {
    var result = await foo()
    console.log(result)
  } catch (e) {}
})()

// Or use Promise syntax:
foo().then(response => {
  console.log(response)
}).catch(error => {
  console.log(error)
})
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src=
"https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

person Fernando Carvajal    schedule 24.01.2018
comment
можно ли использовать эту вторую функцию повторно ?? - person Zum Dummi; 17.02.2019
comment
Как использовать результаты, если вызывается oncolse, log? Разве в этот момент все не попадает в консоль? - person Ken Ingram; 29.06.2019
comment
Это прекрасный и полезный ответ, который ясно демонстрирует, как правильно использовать функцию двойственности async - await. Следует отметить, что async и await фактически не нужны в функции foo(). (Удалите их обоих, и код по-прежнему будет работать нормально.) Это потому, что foo() возвращает обещание, и пока код, получающий обещание, ожидает его, все будет в порядке. ~ * ~ * ~ * ~ Примечание: функция async - await была представлена ​​в ECMA-262, 8-е издание, июнь 2017 г.. - person Henke; 11.05.2021

Ждите

Запрос работает асинхронно, поэтому вы не можете читать данные синхронно, как в обычном коде. Однако, используя async/await, вы можете создать асинхронный код, который выглядит близко / похож на обычный синхронный / последовательный стиль. Код, обрабатывающий данные ответа, должен быть обернут функцией async (load в приведенном ниже фрагменте), и внутри него вам нужно добавить ключевое слово await перед foo() (которое также использует async/await).

async function foo() {
  var url = 'https://jsonplaceholder.typicode.com/todos/1';
  var result = (await fetch(url)).text(); // Or .json()
  return result;
}

async function load() {
  var result = await foo();
  console.log(result);
}

load();

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

person Kamil Kiełczewski    schedule 11.06.2019
comment
Хороший ответ! Очевидно, конструкция async - await была представлена ​​в Спецификации языка ECMAScript 2017 в Июнь 2017 г.. - person Henke; 10.05.2021

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

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

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

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

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

person Haim Zamir    schedule 25.10.2017

Невозможно напрямую вернуть результат ответа Ajax из функции. Причина в том, что вызов Ajax ($.get() или $.post()) является асинхронным, и вызов функции, которая инкапсулирует вызов Ajax, вернется даже до того, как будет отрисован ответ.

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

Вышеупомянутую проблему можно решить двумя способами. Оба используют обещание.

Приведенные ниже фрагменты кода включают URL-адрес JSON. Оба работают, и их можно напрямую скопировать в JSFiddle и протестировать.

Вариант №1 - вернуть вызов Ajax непосредственно из метода foo.
В последней версии jQuery вызов Ajax возвращает объект обещания, который можно разрешить с помощью функции .then. В коде функции .then предшествует функция обратного вызова, которая должна быть разрешена, в данном случае foo().

   // Declare function foo
   function foo(url)
   {
     return $.get(url);
   }

   // Invoke the foo function, which returns a promise object
   // the 'then' function accepts the call back to the resolve function
   foo('https://jsonplaceholder.typicode.com/todos/1')
     .then(function(response)
     {
       console.log(response);
     })
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

Вариант № 2 - объявить объект обещания и вернуть его.
Объявить объект обещания внутри функции, инкапсулировать вызов Ajax в этой функции обещания и вернуть объект обещания.

   function foo1() {
     var promise = new Promise(function(resolve, reject)
     {
       $.ajax({
       url: 'https://jsonplaceholder.typicode.com/todos/1',
       success: function(response) {
           console.log(response);
           resolve(response);
           // return response; // <- I tried that one as well
         }
       });
     });
     return promise;
   }

   foo1()
   .then(function(response)
   {
     console.log('Promise resolved:');
     console.log(response);
   })
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

person Faiz Mohammed    schedule 03.03.2020

1. Первый шаг преткновения

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

let result;

$.ajax({
  url: 'https://jsonplaceholder.typicode.com/todos/1',
  success: function (response) {
    console.log('\nInside $.ajax:');
    console.log(response);
    result = response;
  }
});

console.log('Finally, the result: ' + result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src=
"https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

Упс! Вывод строки console.log('Finally, the result: ' + result);, которая, как я думал, будет напечатана последней, на самом деле печатается перед другим выводом! - И не содержит результата: просто печатает undefined. 1 Как так?

Полезная информация

Я отчетливо помню свой первый ага! момент о том, как понимать асинхронные вызовы.
Это был этот комментарий, говоря:
вы на самом деле не хотите получать данные из обратного вызова;
вы хотите получить действие, требующее данных в обратный вызов!
2
Это очевидно в приведенном выше примере.
Но можно ли писать код после < / em> асинхронный вызов, который обрабатывает ответ после его завершения?

2. Обычный JavaScript и функция обратного вызова

Ответ: да! - возможно.
Альтернативой является использование функции обратного вызова в стиле передачи продолжения: 3

const url = 'https://jsonplaceholder.typicode.com/todos/2';

function asynchronousCall (callback) {
  const request = new XMLHttpRequest();
  request.open('GET', url);
  request.send();
  request.onload = function () {
    if (request.readyState === request.DONE) {
      console.log('The request is done. Now calling back.');
      callback(request.responseText);
    }
  };
}

asynchronousCall(function (result) {
  console.log('This is the start of the callback function. Result:');
  console.log(result);
  console.log('The callback function finishes on this line. THE END!');
});

console.log('LAST in the code, but executed FIRST!');
.as-console-wrapper { max-height: 100% !important; top: 0; }

Обратите внимание, как функция asynchronousCall равна void. Он ничего не возвращает. Вместо этого, вызывая asynchronousCall с анонимной функцией обратного вызова (asynchronousCall(function (result) {...), эта функция выполняет желаемые действия с результатом, но только после завершения запроса - когда responseText доступен.

Выполнение приведенного выше фрагмента показывает, что я, вероятно, не захочу писать какой-либо код после асинхронного вызова (например, строка LAST in the code, but executed FIRST!).
Почему? - Потому что такой код произойдет до, когда асинхронный вызов доставит какие-либо данные ответа.
Это обязательно вызовет путаницу при сравнении кода с выводом .

3. Обещайте .then() - или _15 _ / _ 16_

Конструкция .then() была представлена ​​в 6-м издании ECMA-262 в июне 2015 г., а конструкция _18 _ / _ 19_ была представлена ​​в 8-м издании ECMA-262 в июне 2017 г.. < br /> Приведенный ниже код по-прежнему представляет собой простой JavaScript, заменяющий устаревший XMLHttpRequest на Fetch. 4

fetch('http://api.icndb.com/jokes/random')
  .then(response => response.json())
  .then(responseBody => {
    console.log('.then() - the response body:');
    console.log(JSON.stringify(responseBody) + '\n\n');
  });

async function receiveAndAwaitPromise () {
  const responseBody =
    (await fetch('http://api.icndb.com/jokes/random')).json();
  console.log('async/await:');
  console.log(JSON.stringify(await responseBody) + '\n\n');
}

receiveAndAwaitPromise();
.as-console-wrapper { max-height: 100% !important; top: 0; }

Если вы решите использовать конструкцию _22 _ / _ 23_, следует сделать небольшое предупреждение. Обратите внимание, в приведенном выше фрагменте кода await требуется в двух местах. Если забыть в первую очередь, выхода не будет. Если забыть во втором месте, единственным выходом будет пустой объект {} (или [object Object] или [object Promise]).
Забыть префикс async функции, возможно, хуже всего - результат будет "SyntaxError: missing ) in parenthetical" - без упоминания отсутствующего async ключевого слова.

4. Promise.all - массив URL 5

Предположим, нам нужно запросить целую кучу URL-адресов. Я мог бы отправить один запрос, подождать, пока он ответит, затем отправить следующий запрос, подождать, пока он не ответит, и так далее ...
Ага! - Это может занять много времени. Разве не было бы лучше, если бы я мог отправить их все сразу, а затем ждать не дольше, чем требуется для получения самого медленного ответа?

В качестве упрощенного примера я буду использовать:

urls = ['https://jsonplaceholder.typicode.com/todos/2',
        'https://jsonplaceholder.typicode.com/todos/3']

JSON двух URL:

{"userId":1,"id":2,"title":"quis ut nam facilis et officia qui",
 "completed":false}
{"userId":1,"id":3,"title":"fugiat veniam minus","completed":false}

Цель состоит в том, чтобы получить массив объектов, каждый из которых содержит значение title из соответствующего URL.

Чтобы сделать его немного интереснее, я предполагаю, что уже существует массив имен, с которым я хочу объединить массив результатов URL (заголовки):

namesonly = ['two', 'three']

Желаемый результат - мэшап, объединяющий namesonly и urls в массив объектов:

[{"name":"two","loremipsum":"quis ut nam facilis et officia qui"},
{"name":"three","loremipsum":"fugiat veniam minus"}]

где я изменил имя title на loremipsum.

const namesonly = ['two','three'];

const urls = ['https://jsonplaceholder.typicode.com/todos/2',
  'https://jsonplaceholder.typicode.com/todos/3'];

Promise.all(urls.map(url => fetch(url)
  .then(response => response.json())
  .then(responseBody => responseBody.title)))
  .then(titles => {
    const names = namesonly.map(value => ({ name: value }));
    console.log('names: ' + JSON.stringify(names));
    const latins = titles.map(value => ({ loremipsum: value }));
    console.log('latins:\n' + JSON.stringify(latins));
    const result =
      names.map((item, i) => Object.assign({}, item, latins[i]));
    console.log('result:\n' + JSON.stringify(result));
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

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

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

5. Как визуализировать мэшап в Postman 6

API MusicBrainz содержит информацию об артистах и ​​музыкальных группах.
Пример - a запрос британской рок-группы Coldplay:
http://musicbrainz.org/ws/2/artist/cc197bad-dc9c-440d-a5b5-d52ba2e14234?&fmt=json&inc=url-rels+release-groups.
Ответ JSON содержит, среди прочего, 25 самых ранних названий альбомов группы. Эта информация находится в массиве release-groups. Начало этого массива, включая его первый объект:

...
  "release-groups": [
    {
      "id": "1dc4c347-a1db-32aa-b14f-bc9cc507b843",
      "secondary-type-ids": [],
      "first-release-date": "2000-07-10",
      "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc",
      "disambiguation": "",
      "secondary-types": [],
      "title": "Parachutes",
      "primary-type": "Album"
    },
...

Этот фрагмент JSON показывает, что первый альбом Coldplay - это Parachutes. Он также дает id, в данном случае 1dc4c347-a1db-32aa-b14f-bc9cc507b843, который является уникальным идентификатором альбома.

Этот идентификатор можно использовать для поиска в API архива обложек:
http://coverartarchive.org/release-group/1dc4c347-a1db-32aa-b14f-bc9cc507b843. 7

Для каждого альбома ответ JSON содержит несколько изображений, одно из которых является передней обложкой альбома. Первые несколько строк ответа на вышеуказанный запрос:

{
  "images": [
    {
      "approved": true,
      "back": false,
      "comment": "",
      "edit": 22132705,
      "front": true,
      "id": 4086974851,
      "image": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851.jpg",
      "thumbnails": {
        "250": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg",
        "500": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
        "1200": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-1200.jpg",
        "large": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
= = >   "small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg"
    },
...

Здесь представляет интерес строка "small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg".
Этот URL-адрес является прямой ссылкой на лицевую обложку альбома Parachutes.

Код для создания и визуализации мэшапа

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

const lock = setTimeout(() => {}, 43210);
const albumsArray = [];
const urlsArray = [];
const urlOuter = 'https://musicbrainz.org/ws/2/artist/' +
  pm.collectionVariables.get('MBID') + '?fmt=json&inc=url-rels+release-groups';
pm.sendRequest(urlOuter, (_, responseO) => {
  const bandName = responseO.json().name;
  const albums = responseO.json()['release-groups'];
  for (const item of albums) {
    albumsArray.push(item.title);
    urlsArray.push('https://coverartarchive.org/release-group/' + item.id);
  }
  albumsArray.length = urlsArray.length = 15;
  const images = [];
  let countDown = urlsArray.length;
  urlsArray.forEach((url, index) => {
    asynchronousCall(url, imageURL => {
      images[index] = imageURL;
      if (--countDown === 0) { // Callback for ALL starts on next line.
        clearTimeout(lock); // Unlock the timeout.
        const albumTitles = albumsArray.map(value => ({ title: value }));
        const albumImages = images.map(value => ({ image: value }));
        const albumsAndImages = albumTitles.map(
          (item, i) => Object.assign({}, item, albumImages[i]));
        const template = `<table>
          <tr><th>` + bandName + `</th></tr>
          {{#each responseI}}
          <tr><td>{{title}}<br><img src="{{image}}"></td></tr>
          {{/each}}
        </table>`;
        pm.visualizer.set(template, { responseI: albumsAndImages });
      }
    });
  });
  function asynchronousCall (url, callback) {
    pm.sendRequest(url, (_, responseI) => {
      callback(responseI.json().images.find(obj => obj.front === true)
        .thumbnails.small); // Individual callback.
    });
  }
});


Результат и документация

Результат и документация  в Postman


Как загрузить и запустить коллекцию почтальонов

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

  1. Загрузите и сохраните
    http://henke.atwebpages.com/postman/mbid/MusicBands.pm_coll.json
    в подходящее место на жестком диске.

  2. В Postman Ctrl + O ›Загрузить файлы› MusicBands.pm_coll.json ›Импорт.
    Теперь вы должны увидеть MusicBands среди ваших коллекций в Postman.

  3. Коллекции ›MusicBandsDummyRequestОтправить. 8

  4. В теле ответа почтальона нажмите Визуализировать.

  5. Теперь у вас должна быть возможность прокручивать 15 альбомов, как показано на скриншоте выше.

использованная литература


1 Исходный постер выражает это так: все они возвращают undefined.
2 Если вы думаете, что асинхронные вызовы сбивают с толку, подумайте о том, чтобы посмотрите некоторые вопросы и ответы об асинхронных вызовах, чтобы узнать, помогает ли это.
3 имя XMLHttpRequest так же вводит в заблуждение, как X в AJAX - в наши дни формат данных веб-API повсеместно является JSON, а не XML.
4 Fetch возвращает Promise. Я был удивлен, узнав, что ни XMLHttpRequest, ни Fetch не являются частью стандарта ECMAScript. Причина, по которой JavaScript может получить к ним доступ здесь, заключается в том, что веб-браузер предоставляет их. Стандарт Fetch и стандарт XMLHttpRequest поддерживаются Рабочей группой по технологиям веб-гипертекстовых приложений (WHATWG), созданный в июне 2004 года.
5 В этом разделе многое заимствовано у Как я могу получить массив URL-адресов с помощью Promise.all?.
6 В этом разделе в значительной степени используется Как я могу визуализировать гибридный API API в Postman?.
7 Этот URL автоматически перенаправляется на: https://ia800503.us.archive.org/29/items/mbid-435fc965-9121-461e-b8da-d9b505c9dc9b/index.json.
8 Если вы получили сообщение об ошибке, что-то пошло не так, пока запуская свои скрипты, попробуйте еще раз нажать Отправить.

person Henke    schedule 22.05.2021

Я думаю, что независимо от того, какой метод или механизм используется, или какой фреймворк (Angular / React) скрывает это от вас, соблюдается следующий принцип:

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

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

  3. Когда это успешное прибытие данных или отказ возврата таких данных, зарегистрированные наблюдатели (или обратные вызовы) уведомляются вместе с данными (или вызываются с данными). Если наблюдатель зарегистрирован в виде функции обратного вызова foo, то будет вызываться foo(data). Если наблюдатель зарегистрирован в виде объекта foo, то, в зависимости от интерфейса, может быть вызван foo.notify(data).

person nonopolarity    schedule 10.09.2019
comment
Что вы имеете в виду под "Классическим паттерном наблюдателя". обратный вызов - это не классический шаблон наблюдателя. Возможно, обещание - это вариант классического шаблона наблюдателя или вариант PubSub и т. д. . но ни в коем случае не обратный вызов. по крайней мере, как я это вижу. Вы утверждаете, что все виды асинхронных шаблонов являются реализацией классического шаблона наблюдателя? (событие RXJS Observable не является. хотя rxjs.subject есть) - person Eva Cohen; 17.01.2021
comment
Я имею в виду, что результат не готов, и дайте мне знать, когда он будет готов (или изменился). Итак, на вопрос, как мне вернуть ответ от асинхронного вызова ?, он использует механизм для получения уведомлений, когда есть результат. Я думаю, вы можете сказать, что это другое, потому что шаблон наблюдателя может быть вызван много раз, но этот обратный вызов или обещание выполняется только один раз? Я сосредотачиваюсь на том, чтобы уведомить меня в будущем, когда он будет готов, а не на том, сколько раз он может быть вызван. - person nonopolarity; 19.01.2021
comment
хорошо я спорю только о твоем именовании, о выборе слов. этот обратный вызов является «классическим шаблоном наблюдателя», который представляет собой очень специфический шаблон проектирования, разновидностью которого, можно сказать, является Promise. но обратный вызов (хотя это инструмент для уведомления) не является классическим шаблоном наблюдателя. в обещании мы можем подтолкнуть подписчиков, которые будут уведомлены, когда обещание исполнится, но обратный вызов больше похож на атомарную единицу, а не на шаблон, ИМХО. - person Eva Cohen; 19.01.2021
comment
хорошо, речь идет об именовании ... то, что я хотел передать, было классическим, уведомить меня, когда будет готов вид шаблона - person nonopolarity; 19.01.2021

Первоначально обратные вызовы использовались для асинхронных операций (например, в XMLHttpRequest API < / а>). Теперь API на основе обещаний, такие как Fetch API браузера, стали решение по умолчанию и более удобный синтаксис async/await поддерживается всеми современными браузерами и на Node.js (на стороне сервера).

Распространенный сценарий - получение данных JSON с сервера - может выглядеть так:

async function fetchResource(url) {
  const res = await fetch(url);
  if (!res.ok) {
    throw new Error(res.statusText);
  }
  return res.json();
}

Чтобы использовать его в другой функции:

async function doSomething() {
  try {
    const data = await fetchResource("https://example.test/resource/1");
    // ...
  } catch (e) {
    // Handle error
    ...
  }
}

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

function sleep(timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeout);
  });
}

async function fetchAfterTwoSeconds(url) {
  await sleep(2000);
  return fetchResource(url);
}

В Node.js, который исторически полагался исключительно на обратные вызовы, этот метод настолько распространен, что они добавили вспомогательную функцию под названием util.promisify.

person Philipp Claßen    schedule 12.05.2020

Использование async/await с транспиляторами, такими как Babel, чтобы заставить его работать в старых браузерах. Вам также необходимо установить этот пресет и полифилл Babel из npm: npm i -D babel-preset-env babel-polyfill.

function getData(ajaxurl) { 
  return $.ajax({
    url: ajaxurl,
    type: 'GET',
  });
};

async test() {
  try {
    const res = await getData('https://api.icndb.com/jokes/random')
    console.log(res)
  } catch(err) {
    console.log(err);
  }
}

test();

Или обратный вызов .then - это просто еще один способ написать ту же логику.

getData(ajaxurl).then(function(res) {
    console.log(res)
}
person Murtaza Hussain    schedule 16.04.2019

асинхронный: ложь

Я решил это, установив для async значение false и перестроив мой вызов Ajax:

Я установил глобальную функцию sendRequest(type, url, data) с тремя параметрами, которые будут вызываться каждый раз везде:

function sendRequest(type, url, data) {
    let returnValue = null;
    $.ajax({
        url: url,
        type: type,
        async: false,
        data: data,
        dataType: 'json',
        success: function (resp) {
            returnValue = resp;
        }
    });
    return returnValue;
}

Теперь вызовите функцию:

let password = $("#password").val();
        let email = $("#email").val();
        let data = {
            email: email,
            password: password,
        };
        let  resp =  sendRequest('POST', 'http://localhost/signin')}}", data);
        console.log(resp);

Важное примечание в коде: async: false

Если это решение не работает с вами, обратите внимание, что оно может не работать в некоторых браузерах или в jQuery версии.

person Abd Abughazaleh    schedule 08.11.2020
comment
Это технически решает проблему, но учтите, что это не рекомендуется, потому что это заморозит окно до завершения запроса. Лучше научиться справляться с асинхронной природой JS, чем использовать синхронные версии функций, связанных с вводом-выводом. - person Matt Welke; 31.12.2020
comment
Использование async:false - ужасная практика, и ее нельзя никогда использовать. Поставщики браузеров объявили его устаревшим за несколько лет до того, как был написан этот ответ. Они даже предупреждают вас в консоли инструментов разработчика, чтобы не использовать его при обнаружении. - person charlietfl; 27.03.2021

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

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/

ES6 way (async - await)

(function(){
  async function getJoke(){
    let response = await fetch('http://api.icndb.com/jokes/random');
    let data = await response.json();
    return data.value;
  }

  getJoke().then((joke) => {
    console.log(joke);
  });
})();
person Pallav Khare    schedule 19.11.2020
comment
plnkr: - Мы можем кодировать Angular в браузере с помощью онлайн-редактора под названием plunker. Это дает нам возможность быстро опробовать наш Angular, не требуя сложной настройки. - person Pallav Khare; 12.04.2021

Простой пример кода для преобразования XHR на Node.js в async-await

var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
var xhttp = new XMLHttpRequest();

function xhrWrapWithPromise() {
  return new Promise((resolve, reject) => {
    xhttp.onreadystatechange = function() {
      if (this.readyState == 4) {
        if (this.status == 200) {
          resolve(this.responseText);
        } else {
          reject(new Error("Couldn't feth data finally"));
        }
      }
    };
    xhttp.open("GET", "https://www.w3schools.com/xml/xmlhttp_info.txt", true);
    xhttp.send();
  });
}

// We need to wrap await in Async function so and anonymous IIFE here
(async _ => {
  try {
    let result = await xhrWrapWithPromise();
    console.log(result);
  } catch (error) {
    console.log(error);
  }
})();
person Sumer    schedule 02.12.2018
comment
Я бы рекомендовал использовать fetch вместо упаковки XMLHttpRequest. developer.mozilla.org/en-US/docs/Web/ API / - person Felix Kling; 02.12.2018
comment
Этот код не работает должным образом. Я попробовал это в Stack Snippet, и единственный результат был {}. - person Henke; 09.05.2021

Поскольку await всегда возвращает Promise, просто выполните дополнительное await (внутри функции async), чтобы извлечь значение:

test(); // This alerts "hello"

// This is the outer function that wants to get the string result of inner()
async function test() {
  var str=await await inner();
  alert(str);
} // test

// This ia an inner function that can do arbitrary async operations
async function inner() {
  return Promise.resolve('hello');
}

person David Spector    schedule 31.10.2020
comment
await не возвращает обещание. Он разворачивает обещание и оценивает это значение в обещании. Если то, что вы сказали, правда, почему второй await извлекает значение, а первый await - нет? Если вы используете только один await, вы получите точно такой же результат. - person Felix Kling; 31.10.2020
comment
Если вы нажмете Изменить, затем удалите один из await и, наконец, щелкните Run code snippet, вы увидите, что он отлично работает только с одним await, как объяснено в предыдущем комментарии. (Я не знаю, почему здесь отсутствует кнопка Run code snippet? Это из-за голосов против?) - person Henke; 11.05.2021
comment
Несмотря на то, что лишний await не имеет никакого смысла, я дал этот ответ положительный голос. - Почему? Что ж, в конце концов, он содержит работоспособный пример, демонстрирующий то, о чем просит исходный плакат. Одного этого может быть недостаточно, чтобы гарантировать голосование, но, с другой стороны, здесь есть много ответов, которые не соответствуют такому минимальному требованию. - person Henke; 12.05.2021

person    schedule
comment
Еще один прекрасный ответ, демонстрирующий использование async - await. ~ * ~ Функция async - await была представлена ​​в ECMA-262 8th Выпуск июнь 2017 г.. - person Henke; 11.05.2021