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

avatar
Felix Kling
8 января 2013 в 17:06
1802394
43
6175

У меня есть функция 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`
}
Источник
Sunil Kumar
8 сентября 2021 в 11:26
0

используйте деасинхронизацию, как это coderhelper.com/a/47051880/2083877

Seblor
10 сентября 2021 в 09:14
7

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

Liam
15 октября 2021 в 14:10
0

Не пора ли избавиться от jQuery в этом вопросе? Достаточно ли это наследие в 2021 году?

Felix Kling
15 октября 2021 в 14:13
1

@Liam: это просто пример асинхронной функции, которая принимает обратный вызов.

Liam
15 октября 2021 в 14:36
0

Имеет смысл, я изменил заголовок, чтобы не выделять jQuery.

Ответы (43)

avatar
Felix Kling
8 января 2013 в 17:06
6222

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

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

Проблема

A в Ajax означает асинхронный <710519>. Это означает, что отправка запроса (или, скорее, получение ответа) исключается из обычного потока выполнения. В вашем примере $.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 (Immediately Invoked Function Expression), чтобы запустить контекст async.

Подробнее о async и await можно узнать в MDN.

Вот пример, в котором подробно описывается функция задержки , , 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);
})();

Текущая версия браузера и узел поддерживают версии 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'
});

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

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

foo(myCallback);

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

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

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

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

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

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


ES2015 +: обещает с , затем ()

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

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

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

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

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

delay()
  .then(function(v) { // `delay` returns a promise
    console.log(v); // Log the value once it is resolved
  })
  .catch(function(v) {
    // Or do something else if it is rejected
    // (it would not happen in this example, since `reject` is not called).
  });
.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 Rock - JavaScript Promises.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Без jQuery

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

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

Felix Kling
17 января 2013 в 10:47
88

@Pommy: Если вы хотите использовать jQuery, вы должны включить его. См. docs.jquery.com/Tutorials:Getting_Started_with_jQuery.

cssyphus
6 февраля 2013 в 21:07
17

В решении 1, sub jQuery, я не мог понять эту строку: If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax. (Да, я понимаю, что мой ник в этом случае немного ироничен)

Felix Kling
6 февраля 2013 в 23:29
40

@gibberish: Ммм, я не знаю, как это сделать яснее. Вы видите, как вызывается foo и ему передается функция (foo(function(result) {....});)? result используется внутри этой функции и является ответом на запрос Ajax. Для ссылки на эту функцию первый параметр foo называется callback и назначается success вместо анонимной функции. Итак, $.ajax вызовет callback, когда запрос будет успешным. Я попытался объяснить это немного подробнее.

Chris Moschini
16 апреля 2013 в 02:45
51

Чат для этого вопроса мертв, поэтому я не уверен, где предлагать изложенные изменения, но я предлагаю: 1) Измените синхронную часть на простое обсуждение того, почему это плохо, без примера кода, как это сделать. 2) Удалите / объедините примеры обратного вызова, чтобы показать только более гибкий отложенный подход, который, я думаю, может быть немного проще для тех, кто изучает Javascript.

Felix Kling
8 октября 2015 в 17:44
19

@Jessi: Думаю, вы неправильно поняли эту часть ответа. Вы не можете использовать $.getJSON, если хотите, чтобы запрос Ajax был синхронным. Однако вам не следует, чтобы запрос был синхронным, поэтому это не применимо. Вы должны использовать обратные вызовы или обещания для обработки ответа, как это объясняется ранее в ответе.

Makyen
17 декабря 2016 в 02:38
0

@MadaraUchiha, я категорически не согласен с вашим недавним изменением (большой TL; DR: Use Promises). Этот вопрос является дублирующей целью для большого количества общих вопросов по асинхронной проблеме (как вернуть переменную). Размещение этого кода / информации в начале сбивает с толку людей, которым мы говорим, что это дубликат их проблемы. Код, который вы предоставили, показывает только, как использовать обещание, которое автоматически возвращается jQuery $.ajax(), когда вы не предоставляете обратный вызов. Вы изменили ответ общего назначения на ответ, который теперь относится только к $.ajax() или функциям, которые автоматически возвращают обещание. (Продолжение)

Makyen
17 декабря 2016 в 02:38
0

@MadaraUchiha: (продолжение) Новички теперь считают это слишком конкретным и неприменимым к их вопросу (что может относиться к любой асинхронной функции, а не только к $.ajax()). $.ajax() используется просто как пример более общей проблемы. Обратите внимание, что я сейчас здесь только на этом вопросе / ответе, потому что то, что я описываю как ответ новичков, - это то, что произошло, когда я предложил это как цель дублирования в вопросе. Если бы вы не были модератором, я бы просто отменил правку, пока мы ее обсуждаем, но я потенциально не хочу участвовать в конкурсе отката с модератором.

avatar
Rudresh Oza
8 марта 2022 в 03:15
-1

используйте async & await

Код образца:

const data = async() => {
   const res = await get('https://getdata.com')
}
Community
8 марта 2022 в 09:00
1

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

avatar
Henke
22 мая 2021 в 10:24
27

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

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() - или async / await

Конструкция .then() была представлена ​​в 6-м издании ECMA-262 в июне. 2015 , а конструкция async / await была представлена ​​в ECMA-262 8-е издание, июнь 2017 г. .
Приведенный ниже код по-прежнему представляет собой простой 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; }

Если вы решите использовать async / await, следует сделать предупреждение. построить. Обратите внимание, в приведенном выше фрагменте кода 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 содержит информацию об артистах и ​​музыкальных группах.
Пример - запрос для британской рок-группы Coldplay :
http://musicbrainz.org/ws/2/artist/cc197bad-dc9c-440d-a5b5-d52ba2em14 = 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-aa-b14f-32a -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.
    });
  }
});


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

Result and documentation in Postman


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

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

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

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

  3. Коллекции> MusicBands> DummyRequest> Отправить . 8

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

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

Ссылки


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

avatar
9 апреля 2021 в 13:20
40

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

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

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

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. Это работает, потому что $.ajax jQuery уже возвращает нечто вроде обещания. (На практике, не вдаваясь в подробности, мы бы предпочли обернуть этот вызов, чтобы вернуть реальное обещание, или использовать некоторую альтернативу $.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);
}
avatar
Abd Abughazaleh
8 ноября 2020 в 10:17
2

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

Я решил это, установив для 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.

Matt Welke
31 декабря 2020 в 18:44
0

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

charlietfl
27 марта 2021 в 00:38
2

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

avatar
David Spector
31 октября 2020 в 17:17
-6

Поскольку await всегда возвращает обещание, просто выполните дополнительную 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');
}
Felix Kling
31 октября 2020 в 18:34
12

await не возвращает обещание. Он «разворачивает» обещание и оценивает это значение «в» обещании. Если то, что вы сказали, правда, почему второй await извлекает значение, а первый await - нет? Если вы используете только один await, вы получите точно такой же результат.

Henke
11 мая 2021 в 07:23
2

Если вы нажмете Изменить , затем удалите один из await и, наконец, нажмете Run code snippet, вы увидите, что он отлично работает только с одним await, как объяснено в предыдущем комментарии. (Я не знаю, почему здесь отсутствует кнопка Run code snippet? Это из-за отрицательных голосов?)

Henke
12 мая 2021 в 06:56
1

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

avatar
Philipp Claßen
12 мая 2020 в 09:56
7

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

avatar
Faiz Mohammed
3 марта 2020 в 06:25
11

Невозможно напрямую вернуть результат ответа 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>

avatar
SanjiMika
19 января 2020 в 22:23
27

Прочитав здесь все ответы и поделившись своим опытом, я хотел бы вернуться к деталям 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) !

Bharath Ram
25 августа 2020 в 12:02
0

Привет, хотя содержание этого ответа является точным, он действительно не отвечает на вопрос OP (как вернуть что-то из асинхронного вызова?)

avatar
nonopolarity
10 сентября 2019 в 07:25
7

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

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

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

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

Eva Cohen
17 января 2021 в 19:50
0

что вы имеете в виду, говоря «Это классический образец наблюдателя». обратный вызов не является классическим шаблоном наблюдателя. Возможно, обещание - это вариант классического шаблона наблюдателя или вариант PubSub и т. д. . но ни в коем случае не обратный вызов. по крайней мере, как я это вижу. Вы утверждаете, что все виды асинхронных шаблонов являются реализацией классического шаблона наблюдателя? (событие RXJS Observable не является. хотя rxjs.subject есть)

nonopolarity
19 января 2021 в 04:43
0

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

Eva Cohen
19 января 2021 в 07:04
0

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

nonopolarity
19 января 2021 в 07:08
1

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

avatar
Kamil Kiełczewski
11 июня 2019 в 07:56
18

Ожидание

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

Henke
10 мая 2021 в 08:13
0

Хороший ответ! По-видимому, конструкция async - await была введена в спецификации языка ECMAScript 2017 в июне 2017 года.

avatar
Murtaza Hussain
16 апреля 2019 в 16:13
2

Использование 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)
}
avatar
Amir Fo
7 декабря 2018 в 14:10
30

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

Самый лучший ответ на этот вопрос - использовать 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
});

Но подождите ...!

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

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

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

Неперехваченная ошибка ссылки: обещание не определено

Итак, я решил реализовать свой собственный класс Promise для ES3 ниже компиляторов JavaScript, если он не определен. Просто добавьте этот код перед основным кодом, а затем безопасно используйте 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;
    }();
}
Alaska
28 сентября 2021 в 18:32
0

Я думаю, вы также можете использовать обратный вызов: D, но это невероятно.

avatar
Sumer
2 декабря 2018 в 12:41
-4

Простой пример кода для преобразования 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);
  }
})();
Felix Kling
2 декабря 2018 в 17:01
5

Я бы рекомендовал использовать fetch вместо упаковки XMLHttpRequest. developer.mozilla.org/en-US/docs/Web/API/…

Henke
9 мая 2021 в 14:45
0

Этот код не работает должным образом. Я попробовал это в фрагменте стека, и единственный результат был {}.

avatar
Alex Montoya
12 июля 2018 в 21:48
21

Вот пример, который работает:

const validateName = async userName => {
  const url = "https://jsonplaceholder.typicode.com/todos/1";
  try {
    const response = await axios.get(url);
    return response.data
  } catch (err) {
    return false;
  }
};

validateName("user")
 .then(data => console.log(data))
 .catch(reason => console.log(reason.message))
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src=
"https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
Henke
11 мая 2021 в 12:38
0

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

avatar
Matthew Brent
4 мая 2018 в 15:56
27

Вместо того, чтобы бросать вам код, есть две концепции, которые являются ключевыми для понимания того, как 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()

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

Eva Cohen
17 января 2021 в 19:08
0

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

Matthew Brent
18 января 2021 в 15:58
1

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

Matthew Brent
18 января 2021 в 15:59
1

Обещание в основном бесполезно (но не всегда) без обратного вызова, чтобы что-то сделать с разрешенным значением

avatar
James
17 февраля 2018 в 15:26
43

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

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://coderhelper.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);
Eva Cohen
17 января 2021 в 18:52
0

Считаете ли вы генераторы / асинхронные генераторы только решением асинхронного API? Или вы использовали бы генераторы для обертывания другого асинхронного API, такого как обещание / отложенный? Я согласен, что это еще одно сильное дополнение к асинхронной вселенной, но до сих пор не нашел правильного использования генераторов, которое заставило бы меня принять их.

avatar
Aniket Jha
3 февраля 2018 в 06:06
86

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

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

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

  2. Веб-API

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

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

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

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

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

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

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

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

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

.

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

Callback

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

avatar
Fernando Carvajal
24 января 2018 в 06:18
24

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

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

И выполнив это так.

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

Или синтаксис Promise.

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>
Zum Dummi
17 февраля 2019 в 02:02
0

можно ли эту вторую функцию использовать повторно ??

Ken Ingram
29 июня 2019 в 06:47
0

Как использовать результаты, если вызывается oncolse, log? Разве в этот момент все не попадает в консоль?

Henke
11 мая 2021 в 12:14
0

Это прекрасный и полезный ответ, который ясно демонстрирует, как правильно использовать функцию двойственности async - await. Следует отметить, что async и await фактически не нужны в функции foo(). (Удалите их обоих, и код по-прежнему будет работать нормально.) Это потому, что foo() возвращает обещание, и пока код, получающий обещание , ожидает его , все будет в порядке. ~ * ~ * ~ * ~ Примечание: функция async - await была представлена ​​в 8-м издании ECMA-262 в июне 2017 г..

RAUSHAN KUMAR
21 сентября 2021 в 15:15
0

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

CherryDT
29 октября 2021 в 10:16
0

Это невозможно.

avatar
Pieter Jan Bonestroo
13 января 2018 в 19:13
29

Вопрос был:

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

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

Как сделать асинхронным кодом вида синхронным ?

Решением будет избегать обратных вызовов и использовать комбинацию 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")
Henke
11 мая 2021 в 12:25
0

Любой, кто заинтересован в использовании async / await, вероятно, также захочет прочитать этот ответ (и, возможно, мой комментарий под ним :-).

avatar
Anish K.
31 октября 2017 в 20:12
116

Это очень распространенная проблема, с которой мы сталкиваемся, борясь с «загадками» 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);
     }
  }
edwardsmarkf
26 сентября 2018 в 20:58
0

считается ли это лучшим способом вернуть значение из обещания или async / await?

Anish K.
3 октября 2018 в 16:12
7

@edwardsmarkf Лично я не думаю, что есть лучший способ как таковой. Я использую обещания с then / catch, async / await, а также генераторы для асинхронных частей моего кода. Это во многом зависит от контекста использования.

avatar
Haim Zamir
25 октября 2017 в 03:22
15

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

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

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

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

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

avatar
Khoa Bui
5 июля 2017 в 20:28
28

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

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

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}
Aluan Haddad
18 марта 2018 в 19:48
6

В обратных вызовах или JavaScript нет ничего изначально асинхронного.

Henke
9 мая 2021 в 12:21
0

Зачем оставлять var result; и return result;? Последний по-прежнему будет всегда возвращать undefined!

avatar
mikemaccana
2 июня 2017 в 09:51
84

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

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

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

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

(async function(){

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

})()

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

Michał Perłakowski
8 июня 2017 в 06:47
9

К сожалению, это работает только с функциями, возвращающими обещания - например, это не работает с API Node.js, который использует обратные вызовы. И я бы не рекомендовал использовать его без Babel, потому что не все используют «современные браузеры».

mikemaccana
9 июня 2017 в 18:28
4

Узел 8 @ MichałPerłakowski включает nodejs.org/api/util.html#util_util_promisify_original, который можно использовать, чтобы заставить API node.js возвращать обещания. Есть ли у вас время и деньги для поддержки устаревших браузеров, очевидно, зависит от вашей ситуации.

Juan Mendes
4 октября 2018 в 14:51
0

IE 11 по-прежнему является текущим браузером 2018 года, к сожалению, он не поддерживает await/async

mikemaccana
4 октября 2018 в 14:57
0

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

avatar
amaksr
27 мая 2017 в 02:47
70

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

Дополнительные примеры: , здесь.

J Morris
16 июня 2017 в 23:55
4

Это интересно. Мне нравится, как он позволяет кодировать асинхронные вызовы так, как если бы вы это делали на других языках. Но технически это не настоящий JavaScript?

avatar
Alireza
24 мая 2017 в 09:38
119

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

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

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

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

Например, в вашем случае, когда вы используете jQuery, вы можете сделать что-то вроде этого:

$(document).ready(function(){
    function foo() {
        $.ajax({url: "api/data", success: function(data){
            fooDone(data); // After we have data, we pass it to fooDone
        }});
    };

    function fooDone(data) {
        console.log(data); // fooDone has the data and console.log it
    };

    foo(); // The call happens here
});

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

steve.sims
24 июля 2017 в 06:14
2

Это нормально в глобальной области, но в некотором контексте модуля вы, вероятно, захотите обеспечить правильный контекст для обратного вызова, например. $.ajax({url: "api/data", success: fooDone.bind(this)});

Matthew Brent
4 мая 2018 в 15:57
12

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

Alireza
14 мая 2018 в 07:34
2

@MatthewBrent, вы не ошибаетесь, но и не правы, реквизиты React являются объектами, и если они изменены, они меняются во всем приложении, но разработчик React не рекомендует его использовать ...

avatar
T.J. Crowder
3 мая 2017 в 16:59
171

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

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

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

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

Пример:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", 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, вы можете использовать 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), вы можете использовать async с функцией <18874181575> > for-of и await:

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

Пример:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", 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 + (пока), вы можете использовать вариант шаблона «Сокращение обещаний» (это более сложно, чем обычное сокращение обещаний, потому что мы не передаем результат от одного к другому, но вместо этого собирая их результаты в массив):

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; }
Sarah
28 мая 2017 в 10:21
4

Не могли бы вы объяснить, как работает часть кода if (--expecting === 0)? Версия вашего решения с обратным вызовом отлично работает для меня, я просто не понимаю, как с помощью этого оператора вы проверяете количество завершенных ответов. Цените это просто отсутствие знаний с моей стороны. Есть ли альтернативный способ написания чека?

T.J. Crowder
28 мая 2017 в 18:31
4

@Sarah: expecting начинается со значения array.length, которое указывает количество запросов, которые мы собираемся сделать. Мы знаем, что обратный вызов не будет вызван, пока не будут запущены все эти запросы. В обратном вызове if (--expecting === 0) делает следующее: 1. Уменьшает expecting (мы получили ответ, поэтому мы ожидаем на один ответ меньше), и если значение после , уменьшение равно 0 (мы больше не ждем откликов) готово!

T.J. Crowder
6 мая 2021 в 11:27
1

@Henke - Я думаю, что это действительно личное предпочтение, и хотя обычно я бы предпочел регистрировать необработанные данные и позволять консоли обрабатывать их, в этом конкретном случае я думаю, что вы правы в отношении изменения. Спасибо! :-)

Henke
1 июня 2021 в 14:01
1

Из соображений удобства для себя (и других?) Добавляю ссылку на связанный ответ: Как сделать много асинхронных вызовов и дождаться их всех.

avatar
Mahfuzur Rahman
24 апреля 2017 в 08:09
38

Используйте функцию 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();
avatar
Mohan Dere
13 августа 2016 в 09:36
39

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

  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();
Henke
9 мая 2021 в 15:47
0

Зачем включать фрагмент стека, который выдает ошибку?

avatar
Johannes Fahrenkrug
11 августа 2016 в 14:17
257

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

enter image description here

Shaiju T
31 октября 2016 в 17:48
42

Картинка стоит тысячи слов , Человек A - Спросите у человека B детали, чтобы починить его машину, в свою очередь Человек B - Звонит Ajax и ждет ответа с сервера для деталей ремонта автомобиля, когда получен ответ, функция Ajax Success вызывает функцию Person B и передает ей ответ в качестве аргумента, Person A получает ответ.

Hassan Baig
5 февраля 2018 в 00:32
19

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

barrypicker
22 октября 2019 в 23:42
7

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

Fing Lixon
10 апреля 2021 в 16:52
0

Но с функциями обратного вызова я чувствую, что человека слева в последнем кадре заставляют , а не дать другому человеку свой номер телефона. Вместо этого они должны сказать другому человеку: «Вот все, что я хочу сделать с информацией от чувака по телефону. Делай все это и никогда не говори мне». Что мне не хватает?

Johannes Fahrenkrug
12 апреля 2021 в 20:44
1

@FingLixon Это не идеальный комикс :-D. Второе изображение должно иллюстрировать, что происходит, когда вы пытаетесь прочитать значение слишком рано (до того, как произойдет обратный вызов). Третье изображение иллюстрирует настройку метода обратного вызова: парень слева в основном является обработчиком обратного вызова: он будет вызван с информацией, как только она станет доступной, и затем сможет делать с ней все, что захочет. Теперь я думаю, что было плохой идеей иметь ДВА телефонных звонка в этом комиксе: звонок в магазин и звонок парню слева. Я должен был упростить это, извините за это.

avatar
Francisco Carmona
2 июня 2016 в 08:31
120

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

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);
  });
})();
avatar
Vinoth Rajendran
26 мая 2016 в 13:26
71

Вы можете использовать эту настраиваемую библиотеку (написанную с использованием 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)
});
avatar
Pablo Matias Gomez
22 апреля 2016 в 14:47
91

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

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

$.ajax({
    url: "...",
    success: callback
});
avatar
loretoparisi
12 апреля 2016 в 22:55
92

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    ///// promiseall again

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

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

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

    /////

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

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

avatar
rohithpr
25 января 2016 в 17:43
95

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

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
}

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

Emanegux
7 июня 2017 в 03:19
3

это похоже на функции генератора? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… *

Aluan Haddad
18 марта 2018 в 19:43
3

Это все еще актуально?

rohithpr
20 марта 2018 в 08:18
2

Вы можете использовать async-await, если используете некоторые из новейших версий node. Если кто-то застрял на более старых версиях, они могут использовать этот метод.

avatar
David R Tribble
23 сентября 2015 в 22:52
39

Краткий ответ : ваш метод 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.

Felix Kling
23 сентября 2015 в 22:53
15

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

avatar
jsbisht
2 сентября 2015 в 12:54
108

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

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

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

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

Felix Kling
2 сентября 2015 в 13:18
12

Здесь нет ничего особенного в использовании объекта. Это также сработает, если вы назначите ответ напрямую на result. Это работает, потому что вы читаете переменную после того, как функция async завершена.

avatar
Benjamin Gruenbaum
12 мая 2015 в 02:22
344

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

Это означает AngularJS, jQuery (с отложенным), замену встроенного XHR (выборка), Ember.js, Backbone6091.js или любую библиотеку Node.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 всегда выполняются асинхронно, то есть после кода под ними, которого нет в a2062 673860 > обработчик.

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

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

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

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

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

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

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

С обещаниями

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

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

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

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

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

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

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

Это то же самое, что и ваш исходный вызов API, вы можете:

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

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

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

ES2015 (ES6)

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

function* foo(){ // Notice the star. This is ES6, so new browsers, 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
}

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

avatar
Maleen Abewardana
26 августа 2014 в 08:11
174

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

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

Benjamin Gruenbaum
4 ноября 2014 в 02:29
18

Однако это не объясняет, как обещания решат эту проблему.

Tracker1
19 февраля 2015 в 19:24
7

Оба метода jQuery и fetch также возвращают обещания. Я предлагаю пересмотреть ваш ответ. Хотя jQuery не совсем то же самое (тогда есть, но уловка нет).

avatar
Nic
23 мая 2014 в 02:05
270

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

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

Jacques ジャック
4 января 2016 в 15:49
16

Этот ответ полностью семантический ... ваш метод успеха - это просто обратный вызов в обратном вызове. Вы можете просто иметь success: handleData, и это сработает.

avatar
Hemant Bavle
18 февраля 2014 в 18:58
252

Простейшее решение - создать функцию 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);
});
Hemant Bavle
28 марта 2014 в 18:12
9

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

Benjamin Gruenbaum
10 апреля 2014 в 09:18
18

Извините, я забыл оставить комментарий (обычно так и делаю!). Я проголосовал против. Голоса против не указывают на фактическую правильность или отсутствие, они указывают на полезность в контексте или отсутствие. Я не считаю ваш ответ полезным, учитывая, что Феликс уже объясняет это гораздо более подробно. Кстати, зачем вам строчить ответ, если это JSON?

Hemant Bavle
10 апреля 2014 в 10:27
9

хорошо .. @Benjamin Я использовал stringify, чтобы преобразовать объект JSON в строку. И спасибо, что разъяснили вашу точку зрения. Буду иметь в виду публиковать более подробные ответы.

pesho hristov
19 февраля 2016 в 16:02
0

А что, если вы хотите вернуть responseObj вне successCallback ... :) ... как вы это сделаете ...? ... потому что простой return вернет его к "успешному" обратному вызову ajax ... а не за пределами "successCallback" ...

avatar
cocco
19 августа 2013 в 08:06
424

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 как буфер массива больших двоичных объектов или что-то еще ...

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

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

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

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

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

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

Есть только this.type, для которого задана ошибка ошибка .

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

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

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


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

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

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

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

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

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

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

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

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


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

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

Да, можно.

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

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

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

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

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

Benjamin Gruenbaum
23 августа 2013 в 05:56
19

Хотя этот ответ хорош (и все мы любим XHR2, и публикация файловых данных и составных данных - это просто потрясающе) - это показывает синтаксический сахар для публикации XHR с помощью JavaScript - вы можете поместить это в сообщение в блоге ( Я бы хотел) или даже в библиотеке (не уверен, что имя x, ajax или xhr может быть лучше :)). Я не понимаю, как он отвечает на возврат ответа от вызова AJAX. (кто-то мог еще сделать var res = x("url") и не понять, почему не работает;)). Кстати, было бы здорово, если бы вы вернули c из метода, чтобы пользователи могли подключиться к error и т. Д.

Benjamin Gruenbaum
23 августа 2013 в 17:28
29

2.ajax is meant to be async.. so NO var res=x('url').. В этом весь смысл этого вопроса и ответов :)

stone
8 октября 2017 в 06:20
16

@cocco Значит, вы написали вводящий в заблуждение, нечитаемый код в SO ответе , чтобы сэкономить несколько нажатий клавиш? Пожалуйста, не делай этого.

avatar
Benjamin Gruenbaum
29 мая 2013 в 23:30
1155

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

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

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

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

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

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


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

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

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

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

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

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

(Fiddle)

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

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

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

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

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

Использование будет следующим:

getFive(onComplete);

Что должно предупредить "5" на экране. (Скрипка).

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

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

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

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

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

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

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

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

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

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

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

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

Итак:

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

становится:

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

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

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

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

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

Matthew G
16 августа 2013 в 05:54
25

«синхронные запросы блокируют выполнение кода и могут вызывать утечку памяти и событий» как может синхронный запрос утечка памяти?