Как вызвать асинхронную функцию JavaScript и заблокировать исходный вызывающий
У меня есть интересная ситуация, когда мой обычно умный ум не смог найти решение для:) Вот ситуация...
У меня есть класс, у которого есть метод get()... этот метод вызывается для получения сохраненных пользовательских настроек... то, что он делает, - это вызов некоторого базового провайдера для фактического получения данных... как написано сейчас, он вызывает поставщика, который говорит куки... поэтому, get() вызывает providerGet(), пусть say, providerGet() возвращает значение и get() передает его вместе с вызывающим. Вызывающий абонент ожидает ответа, прежде чем он продолжит свою работу, очевидно.
Вот сложная часть... Я сейчас пытаюсь реализовать провайдера, который является асинхронным по своей природе (в этом случае используется локальное хранилище)... поэтому, providerGet() сразу вернется, отправив вызов на локальный хранилище, которое через некоторое время вызовет функцию обратного вызова, которая была передана ему... но, поскольку providerGet() уже возвращен, и, таким образом, get() теперь по расширению к оригиналу вызывал, он, очевидно, не возвратил фактические извлеченные данные.
Итак, вопрос в том, просто ли способ по существу "заблокировать" возврат из providerGet(), пока асинхронный вызов не вернется? Обратите внимание, что для моих целей я не занимаюсь последствиями производительности, которые могут иметь это, я просто пытаюсь понять, как заставить его работать.
Я не думаю, что есть способ, конечно, я знаю, что мне не удалось это сделать... поэтому я хотел бросить его и посмотреть, что другие люди могут придумать:)
edit: Я только сейчас изучаю, что суть проблемы, тот факт, что веб-интерфейс sql API является асинхронным, может иметь решение... также появляется синхронная версия API, что я и сделал Я понимаю, что сейчас я читаю документы, чтобы узнать, как их использовать, но это решило бы проблему, потому что единственная причина, по которой providerGet() была написана асинхронно, заключалась в том, чтобы разрешить этому провайдеру... код что get() является частью моего собственного уровня абстракции над различными поставщиками хранилища (куки, веб-sql, localStorage и т.д.), поэтому самый низкий общий знаменатель должен побеждать, а это значит, что если он является асинхронным, они ВСЕ должны быть асинхронными.. только один был это веб-SQL... так что если есть способ сделать это синхронно моя точка становится спорным (еще интересный вопрос, в общем, я думаю, хотя)
edit2: Хорошо, никакой помощи там кажется... похоже, что синхронная версия API не реализована ни в одном браузере, и даже если бы она указала, что она может использоваться только из рабочих потоков, так что это похоже, это не помогло. Хотя, читая некоторые другие вещи, кажется, что есть способ вытащить этот трюк, используя рекурсию... Теперь я собираю несколько тестовых кодов, я отправлю его, если/когда я его заработаю, кажется очень интересным способ обойти любую такую ситуацию в целом.
edit3: В соответствии с моими комментариями ниже, действительно нет способа сделать именно то, что я хотел. Решение, с которым я столкнулся, чтобы решить мою непосредственную проблему, - это просто не разрешать использование веб-SQL для хранения данных. Это не идеальное решение, но поскольку эта спецификация находится в движении и не широко внедрена в любом случае, это не конец света... надеюсь, когда будет правильно поддерживаться синхронная версия, и я могу подключить к ней нового провайдера и хорошо идти. В общем, хотя, похоже, нет никакого способа вытащить это чудо... подтверждает то, что я ожидал, это было так, но хотелось бы, чтобы я ошибся в этот раз:)
Ответы
Ответ 1
Нет, вы не можете заблокировать, пока не завершится асинхронный вызов. Это так просто.
Похоже, вы уже знаете это, но если вы хотите использовать асинхронные вызовы ajax, вам придется реструктурировать способ использования вашего кода. Вы не можете просто иметь метод .get(), который выполняет асинхронный вызов ajax, блокирует до его завершения и возвращает результат. Шаблон проектирования, наиболее часто используемый в этих случаях (например, посмотрите на все API-интерфейсы Google javascript, которые выполняют сетевое взаимодействие), заключается в том, чтобы вызывающая сторона передавала вам функцию завершения. Вызов .get()
запустит асинхронную операцию и сразу же вернется. Когда операция завершится, будет вызвана функция завершения. Вызывающий должен соответствующим образом структурировать свой код.
Вы просто не можете написать прямой последовательный процедурный код javascript при использовании асинхронных сетей, таких как:
var result = abc.get()
document.write(result);
Наиболее распространенный шаблон дизайна выглядит следующим образом:
abc.get(function(result) {
document.write(result);
});
Если ваша проблема в нескольких уровнях вызова, то обратные вызовы могут передаваться на разные уровни и вызываться при необходимости.
К вашему сведению, новые браузеры поддерживают концепцию обещаний, которые затем можно использовать с async
и await
написания кода, который может выглядеть следующим образом:
async function someFunc() {
let result = await abc.get()
document.write(result);
}
Это все еще асинхронно. Это все еще не блокирует. abc.get()
должен возвращать обещание, которое разрешается до значения result
. Этот код должен находиться внутри функции, которая объявлена как async
и другой код за пределами этой функции будет продолжать выполняться (что делает эту неблокирующую). Но вы можете написать код, который "выглядит" больше как блокирующий код, если он локально для конкретной функции, в которой он содержится.
Ответ 2
создайте поток webworker, чтобы выполнить операцию async для вас.
передать ему информацию, она должна выполнить задачу плюс уникальный идентификатор.
трюк заключается в том, чтобы отправить результат на веб-сервер, когда он закончит.
тем временем... функция, которая породила веб-исполнителя, отправляет запрос ajax на тот же веб-сервер
используйте синхронный флаг объекта xmlhttprequest (да, он имеет синхронную опцию). поскольку он будет блокироваться до тех пор, пока HTTP-запрос не будет завершен, вы можете просто иметь свой веб-сервер script опросить базу данных для обновлений или что-то еще до тех пор, пока результат не будет отправлен на него.
Уродливо, я знаю. но он будет блокировать без hogging cpu: D
в основном
function get(...) {
spawnWebworker(...);
var xhr = sendSynchronousXHR(...);
return xhr.responseTEXT;
}
Ответ 3
Почему бы просто не перевести исходный вызывающий абонент в свой собственный ответ на get()
? Этот обратный вызов будет содержать код, который зависит от ответа.
Метод get()
переадресует обратный вызов на providerGet()
, который затем вызывает его, когда он вызывает свой собственный обратный вызов.
Результат выборки будет передан в исходный обратный вызов вызывающего абонента.
function get( arg1, arg2, fn ) {
// whatever code
// call providerGet, passing along the callback
providerGet( fn );
}
function providerGet( fn ) {
// do async activity
// in the callback to the async, invoke the callback and pass it the data
// ...
fn( received_data );
// ...
}
get( 'some_arg', 'another_arg', function( data ) {
alert( data );
});
Ответ 4
Когда ваш метод асинхронизации запускается, я бы открыл какой-то модальный диалог (который пользователь не может закрыть), сообщив им, что запрос находится в процессе. Когда запрос завершится, закройте модальный код вашего обратного вызова.
Один из возможных способов сделать это - jqModal, , но для этого потребуется загрузить jQuery в ваш проект. Я не уверен, что это вариант для вас или нет.
Ответ 5
Это некрасиво, но в любом случае я думаю, что вопрос является добрым, подразумевая, что нужно уродливое решение...
- В вашей функции get сериализуйте свой запрос в строку.
- Откройте iframe, передав (A) этот сериализованный запрос и (B) случайное число в querystring для этого iframe
- В вашем iframe есть код javascript, который считывает SQL-запрос и номер из его собственного запроса
- Ваш iframe асинхронно начинает выполнение запроса.
- Когда ваш запрос iframe выполняется асинхронно, он отправляет его вместе со случайным числом на ваш сервер, скажем /write.php?rand = ### & reslt = "blahblahblah"
- Write.php сохраняет эту информацию где-то
- Вернувшись в ваш основной script, после создания и загрузки iframe вы создаете синхронный запрос AJAX на ваш сервер, скажем в /read.php?rand = ####
- /read.php блокирует до тех пор, пока не будет доступна письменная информация, а затем вернет ее на главную страницу
В качестве альтернативы, чтобы избежать отправки данных по сети, вместо этого вы можете преобразовать результат iframe в изображение, созданное с помощью canvas, которое кэширует браузер (подобно тому, как, по сообщениям, использовался файл Zombie cookie). Затем ваша блокировка script будет пытаться постоянно загружать это изображение снова и снова (с небольшим сетевым задержкой по каждому запросу) до тех пор, пока не будет доступна кешированная версия, которую вы могли бы распознать с помощью некоторого флага, который вы указали для указания это сделано.