Вызов асинхронной функции Javascript синхронно - PullRequest
187 голосов
/ 03 февраля 2012

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

Хорошо, что из этого, как мне сделать так, чтобы я мог:

function doSomething() {

  var data;

  function callBack(d) {
    data = d;
  }

  myAsynchronousCall(param1, callBack);

  // block here and return data when the callback is finished
  return data;
}

Все примеры (или их отсутствие) используют библиотеки и / или компиляторы, которые не подходят для этого решения. Мне нужен конкретный пример того, как заставить его блокироваться (например, НЕ оставлять функцию doSomething до тех пор, пока не будет вызван обратный вызов) БЕЗ зависания интерфейса. Если такое возможно в JS.

Ответы [ 7 ]

119 голосов
/ 03 февраля 2012

"не говорите мне о том, как я должен сделать это" правильным образом "или как-то еще"

ОК. но вы действительно должны сделать это правильно ... или что-то еще

"Мне нужен конкретный пример того, как сделать это блоком... БЕЗ замораживания пользовательского интерфейса. Если такое возможно в JS. "

Нет, невозможно заблокировать работающий JavaScript без блокировки пользовательского интерфейса.

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

function doSomething() {

      // callback sets the received data to a global var
  function callBack(d) {
      window.data = d;
  }
      // start the async
  myAsynchronousCall(param1, callBack);

}

  // start the function
doSomething();

  // make sure the global is clear
window.data = null

  // start polling at an interval until the data is found at the global
var intvl = setInterval(function() {
    if (window.data) { 
        clearInterval(intvl);
        console.log(data);
    }
}, 100);

Все это предполагает, что вы можете изменить doSomething().Я не знаю, есть ли это в карточках.

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


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

function doSomething( func ) {

  function callBack(d) {
    func( d );
  }

  myAsynchronousCall(param1, callBack);

}

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

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

Конечно, если это единственное, что делает обратный вызов, вы просто передаете func напрямую ...

myAsynchronousCall(param1, func);
47 голосов
/ 03 февраля 2012

Посмотрите на Обещания JQuery:

http://api.jquery.com/promise/

http://api.jquery.com/jQuery.when/

http://api.jquery.com/deferred.promise/

Измените код:


    var dfd = new jQuery.Deferred();


    function callBack(data) {
       dfd.notify(data);
    }

    // do the async call.
    myAsynchronousCall(param1, callBack);

    function doSomething(data) {
     // do stuff with data...
    }

    $.when(dfd).then(doSomething);


44 голосов
/ 07 ноября 2015

Асинхронные функции , функция в ES2017 , позволяют синхронизировать асинхронный код с помощью обещаний (особая форма асинхронного кода) и awaitключевое слово.Также обратите внимание на примеры кода ниже ключевого слова async перед ключевым словом function, которое обозначает функцию async / await.Ключевое слово await не будет работать без функции, предварительно фиксированной с ключевым словом async.Поскольку в настоящее время нет исключения из этого, это означает, что никакие ожидания верхнего уровня не будут работать (верхний уровень ожидает, что означает ожидание вне какой-либо функции).Хотя есть предложение для верхнего уровня await.

ES2017 был утвержден (то есть окончательно доработан) как стандарт для JavaScript 27 июня 2017 года. Асинхронное ожидание может уже работать в вашембраузер, но если нет, вы все равно можете использовать эту функциональность с помощью транспортера javascript, например babel или traceur .Chrome 55 имеет полную поддержку асинхронных функций.Так что, если у вас более новый браузер, вы можете попробовать код ниже.

См. таблицу совместимости kangax es2017 для совместимости браузера.

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

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

async function doAsync () {
  var start = Date.now(), time;
  console.log(0);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
}

doAsync();

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

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

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this calls each promise returning function one after the other
async function doAsync () {
  var response = [];
  var start = Date.now();
  // each index is a promise returning function
  var promiseFuncs= [doSomethingAsync, doSomethingAsync, doSomethingAsync];
  for(var i = 0; i < promiseFuncs.length; ++i) {
    var promiseFunc = promiseFuncs[i];
    response.push(await promiseFunc() - start);
    console.log(response);
  }
  // do something with response which is an array of values that were from resolved promises.
  return response
}

doAsync().then(function (response) {
  console.log(response)
})

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

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

// no change
function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

// no change
function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this function calls the async promise returning functions all at around the same time
async function doAsync () {
  var start = Date.now();
  // we are now using promise all to await all promises to settle
  var responses = await Promise.all([doSomethingAsync(), doSomethingAsync(), doSomethingAsync()]);
  return responses.map(x=>x-start);
}

// no change
doAsync().then(function (response) {
  console.log(response)
})

Если обещание, возможно, отклоняется, вы можете заключить его в попытку try или пропустить попытку try и позволить ошибке распространиться на вызов catch функций async / await.Вы должны быть осторожны, чтобы не оставлять ошибки в обещаниях необработанными, особенно в Node.js.Ниже приведены некоторые примеры, показывающие, как работают ошибки.

function timeoutReject (time) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      reject(new Error("OOPS well you got an error at TIMESTAMP: " + Date.now()));
    }, time)
  })
}

function doErrorAsync () {
  return timeoutReject(1000);
}

var log = (...args)=>console.log(...args);
var logErr = (...args)=>console.error(...args);

async function unpropogatedError () {
  // promise is not awaited or returned so it does not propogate the error
  doErrorAsync();
  return "finished unpropogatedError successfully";
}

unpropogatedError().then(log).catch(logErr)

async function handledError () {
  var start = Date.now();
  try {
    console.log((await doErrorAsync()) - start);
    console.log("past error");
  } catch (e) {
    console.log("in catch we handled the error");
  }
  
  return "finished handledError successfully";
}

handledError().then(log).catch(logErr)

// example of how error propogates to chained catch method
async function propogatedError () {
  var start = Date.now();
  var time = await doErrorAsync() - start;
  console.log(time - start);
  return "finished propogatedError successfully";
}

// this is what prints propogatedError's error.
propogatedError().then(log).catch(logErr)

Если вы перейдете здесь , вы можете увидеть готовые предложения для будущих версий ECMAScript.

Альтернативой этому, которая может использоваться только с ES2015 (ES6), является использование специальной функции, которая оборачивает функцию генератора. Функции генератора имеют ключевое слово yield, которое можно использовать для репликации ключевого слова await с окружающей функцией. Ключевое слово yield и функция генератора имеют гораздо более общее назначение и могут выполнять гораздо больше функций, чем просто функция асинхронного ожидания. Если вы хотите обертку функции генератора, которая может использоваться для репликации асинхронного ожидания, я бы проверил co.js . Кстати, функция co, очень похожая на функции асинхронного ожидания, возвращает обещание. Честно говоря, на данный момент совместимость браузера примерно одинакова как для функций генератора, так и для асинхронных функций, поэтому, если вы просто хотите использовать функцию асинхронного ожидания, вы должны использовать функции асинхронной передачи без co.js.

Поддержка браузера на самом деле довольно хороша для функций Async (по состоянию на 2017 год) во всех основных современных браузерах (Chrome, Safari и Edge), кроме IE.

6 голосов
/ 11 июня 2013

Есть один хороший обходной путь на http://taskjs.org/

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

Вот пример кода из проекта GitHub

var { Deferred } = task;

spawn(function() {
    out.innerHTML = "reading...\n";
    try {
        var d = yield read("read.html");
        alert(d.responseText.length);
    } catch (e) {
        e.stack.split(/\n/).forEach(function(line) { console.log(line) });
        console.log("");
        out.innerHTML = "error: " + e;
    }

});

function read(url, method) {
    method = method || "GET";
    var xhr = new XMLHttpRequest();
    var deferred = new Deferred();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status >= 400) {
                var e = new Error(xhr.statusText);
                e.status = xhr.status;
                deferred.reject(e);
            } else {
                deferred.resolve({
                    responseText: xhr.responseText
                });
            }
        }
    };
    xhr.open(method, url, true);
    xhr.send();
    return deferred.promise;
}
0 голосов
/ 04 апреля 2019

Вы можете заставить асинхронный JavaScript в NodeJS быть синхронным с sync-rpc .

Это, безусловно, замораживает ваш пользовательский интерфейс, так что я все еще скептик, когда дело доходит до того, нужно ли использовать ярлык, который вам нужен. Невозможно приостановить единый поток в JavaScript, даже если NodeJS позволяет вам иногда блокировать его. Никакие обратные вызовы, события или что-либо асинхронное вообще не смогут обрабатываться, пока ваше обещание не разрешится. Поэтому, если вы, читатель, не столкнетесь с неизбежной ситуацией, такой как OP (или, в моем случае, не пишете прославленный скрипт оболочки без обратных вызовов, событий и т. Д.), НЕ ДЕЛАЙТЕ ЭТОГО!

Но вот как вы можете это сделать:

./calling-file.js

var createClient = require('sync-rpc');
var mySynchronousCall = createClient(require.resolve('./my-asynchronous-call'), 'init data');

var param1 = 'test data'
var data = mySynchronousCall(param1);
console.log(data); // prints: received "test data" after "init data"

./my-asynchronous-call.js

function init(initData) {
  return function(param1) {
    // Return a promise here and the resulting rpc client will be synchronous
    return Promise.resolve('received "' + param1 + '" after "' + initData + '"');
  };
}
module.exports = init;

ОГРАНИЧЕНИЕ:

Оба являются следствием того, как реализован sync-rpc, то есть путем злоупотребления require('child_process').spawnSync:

  1. Это не будет работать в браузере.
  2. Аргументы вашей функции должны быть сериализуемыми. Ваши аргументы будут входить и выходить из JSON.stringify, поэтому функции и неперечислимые свойства, такие как цепочки прототипов, будут потеряны.
0 голосов
/ 29 августа 2018

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

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

Подробнее о асинхронных функциях

async function myAsynchronousCall(param1) {
    // logic for myAsynchronous call
    return d;
}

function doSomething() {

  var data = await myAsynchronousCall(param1); //'blocks' here until the async call is finished
  return data;
}
0 голосов
/ 06 февраля 2018

Использование Async Await и Promise.resolve / Promise.all ?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...