синхронный - функции обратного вызова javascript




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

Js однопоточный.

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

1) Event Loop

2) Веб-API

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

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

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

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

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

Изображение из одного из ответов здесь, Правильно объясняет использование обратного вызова ... Мы передаем нашу функцию (функцию, использующую данные, возвращенные с сервера) функции, вызывающей сервер.

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

Читайте здесь о новых методах в ECMA (2016/17) для выполнения асинхронных вызовов (@Felix Kling Answer on Top) https://stackoverflow.com/a/14220323/7579856

У меня есть функция foo которая делает запрос Ajax. Как я могу вернуть ответ от foo ?

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

function foo() {
    var result;

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

    return result;
}

var result = foo(); // It always ends up being `undefined`.

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

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

Эта проблема

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

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

синхронный

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

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

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

var item = findItem();

// Do something with item
doSomethingElse();

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

Асинхронный

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

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

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

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

Решение (s)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

становится

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

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

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

foo(myCallback);

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

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

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

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

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

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

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

then() - это новая функция 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).
  });

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

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

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

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

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

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

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

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

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

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

Примечание: обещание получилось

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

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

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

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

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

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

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

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

Без jQuery

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

JQuery

Если вы используете jQuery , вы можете установить для параметра async значение false . Обратите внимание, что эта опция устарела начиная с jQuery 1.8. Затем вы можете по-прежнему использовать success обратный вызов или получить доступ к свойству responseText объекта jqXHR :

function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}

Если вы используете любой другой метод jQuery Ajax, такой как $.get , $.getJSON и т. Д., Вы должны изменить его на $.ajax (поскольку вы можете передавать только параметры конфигурации в $.ajax ).

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


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

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

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

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

(async function(){

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

})()

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


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

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

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

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

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

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

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

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

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

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

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

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

(Fiddle)

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

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

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

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

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

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

getFive(onComplete);

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

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

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

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

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

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

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

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

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

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

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

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

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

Так:

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

становится:

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

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

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

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

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

(fiddle)

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

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


Angular1

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

Here говорится,

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

Вы можете найти хорошее объяснение here .

Пример найден в docs упомянутых ниже.

  promiseB = promiseA.then(
    function onSuccess(result) {
      return result + 1;
    }
    ,function onError(err) {
      //Handle error
    }
  );

 // promiseB will be resolved immediately after promiseA is resolved 
 // and its value will be the result of promiseA incremented by 1.

Angular2 и позже

В Angular2 с видом на следующем примере, но его recommended использовать Observables с Angular2 .

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

}

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

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

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

Кроме того, здесь приведена spec обещаний .


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

  1. then()
  2. Q - библиотека обещаний для JavaScript
  3. A + Promises.js
  4. JQuery отложено
  5. API XMLHttpRequest
  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();


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

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

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

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

Пример:

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

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

Или вот версия, возвращающая Promise вместо:

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

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

Пример:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

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

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

Если doSomethingAsync дает вам then() , вы можете использовать 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:", 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:", 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/await с for-of и await :

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

Пример:

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

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

Если вы не можете использовать синтаксис ES2017 + (пока), вы можете использовать вариант шаблона «Promise Reduce» (это более сложный вариант, чем обычное сокращение Promise, поскольку мы не передаем результат из одного в другое, а вместо этого собирая их результаты в массив):

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Пример:

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

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

... что менее сложно с функциями ES2015 + стрелка :

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

Пример:

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

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


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

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

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

Контекст. Пример запрашивает конечную точку Spotify Web API для поиска playlist объектов по заданному набору строк запроса:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    ///// promiseall again

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

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

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

    /////

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

Я подробно обсудил это решение here .


Вопрос был:

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

что МОЖЕТ быть интерпретировано как:

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

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

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

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


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

Дополнительные примеры здесь: https://github.com/amaksr/nsynjs/tree/master/examples


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

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

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

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


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

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}

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

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

$.ajax({
    url: "...",
    success: callback
});

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

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
}

Вы можете оформить заказ here .


Самое простое решение - создать функцию JavaScript и вызвать ее для success обратного вызова Ajax .

function callServerAsync(){
    $.ajax({
        url: '...',
        success: function(response) {

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}

function foo(callback) {

    $.ajax({
        url: '...',
        success: function(response) {
           return callback(null, response);
        }
    });
}

var result = foo(function(err, result){
          if (!err)
           console.log(result);    
}); 

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

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

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

function foo(){
   return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
    setTimeout ( function(){ 
      // promise is RESOLVED , when 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 значения (которое вы намереваетесь вернуть). Таким образом, короче говоря, вы разрешаете значение, а не возвращаете его в асинхронных функциях.

ОБНОВЛЕНИЕ (Обещания с асинхронным ожиданием)

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

затем / поймать версию:

function fetchUsers(){
   let users = [];
   getUsers()
   .then(_users => users = _users)
   .catch(err =>{
      throw err
   })
   return users;
 }

версия async / await:

  async function fetchUsers(){
     try{
        let users = await getUsers()
        return users;
     }
     catch(err){
        throw err;
     }
  }

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


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


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

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 http не будет выполнен, а затем console.log (res.joke) (как обычный асинхронный поток).

Это плнкр:

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





event-loop