如何调用异步 JavaScript 函数并阻止原始调用者
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8448218/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to call an asynchronous JavaScript function and block the original caller
提问by Frank W. Zammetti
I have an interesting situation that my usually clever mind hasn't been able to come up with a solution for :) Here's the situation...
我有一个有趣的情况,我通常聪明的头脑一直无法想出解决方案:) 这是情况......
I have a class that has a get() method... this method is called to get stored user preferences... what it does is calls on some underlying provider to actually get the data... as written now, it's calling on a provider that talks cookies... so, get() calls providerGet() let's say, providerGet() returns a value and get() passes it along to the caller. The caller expects a response before it continues its work obviously.
我有一个具有 get() 方法的类……调用此方法是为了获取存储的用户首选项……它所做的是调用某些底层提供程序以实际获取数据……正如现在所写,它正在调用一个谈论 cookie 的提供者......所以,get() 调用 providerGet() 假设,providerGet() 返回一个值,get() 将它传递给调用者。调用者在明显地继续其工作之前期望得到响应。
Here's the tricky part... I now am trying to implement a provider that is asychronous in nature (using local storage in this case)... so, providerGet() would return right away, having dispatched a call to local storage that will, some time later, call a callback function that was passed to it... but, since providerGet() already returned, and so did get() now by extension to the original called, it obviously hasn't returned the actual retrieved data.
这是棘手的部分......我现在正在尝试实现一个本质上是异步的提供者(在这种情况下使用本地存储)......因此, providerGet() 将立即返回,已向本地存储发送了一个调用,该调用将,一段时间后,调用传递给它的回调函数......但是,由于 providerGet() 已经返回,并且 get() 现在通过扩展到原始调用,它显然没有返回实际检索到的数据.
So, the question is simply is there a way to essentially "block" the return from providerGet() until the asychronous call returns? Note that for my purposes I'm not concerned with the performance implications this might have, I'm just trying to figure out how to make it work.
所以,问题只是有没有办法从本质上“阻止” providerGet() 的返回,直到异步调用返回?请注意,出于我的目的,我并不关心这可能带来的性能影响,我只是想弄清楚如何使其工作。
I don't think there's a way, certainly I know I haven't been able to come up with it... so I wanted to toss it out and see what other people can come up with :)
我不认为有办法,当然我知道我无法想出它......所以我想把它扔掉,看看其他人能想出什么:)
edit: I'm just learning now that the core of the problem, the fact that the web sql API is asychronous, may have a solution... turns out there's a synchronous version of the API as well, something I didn't realize... I'm reading through docs now to see how to use it, but that would solve the problem nicely since the only reason providerGet() was written asychronously at all was to allow for that provider... the code that get() is a part of is my own abstraction layer above various storage providers (cookies, web sql, localStorage, etc) so the lowest common denominator has to win, which means if one is asychronous they ALL have to be asychronous... the only one that was is web sql... so if there's a way to do that synchronously my point become moot (still an interesting question generically I think though)
编辑:我现在才知道问题的核心,即 web sql API 是异步的这一事实,可能有一个解决方案......原来还有一个 API 的同步版本,这是我没有意识到的...我现在正在阅读文档以了解如何使用它,但这将很好地解决问题,因为 providerGet() 完全异步编写的唯一原因是允许该提供程序......获取( ) 是我自己在各种存储提供程序(cookies、web sql、localStorage 等)之上的抽象层的一部分,所以最小的公分母必须获胜,这意味着如果一个是异步的,它们都必须是异步的......唯一的一个是 web sql ......所以如果有一种方法可以同步做到这一点,我的观点就变得没有实际意义(尽管我认为这仍然是一个有趣的问题)
edit2: Ah well, no help there it seems... seems like the synchronous version of the API isn't implemented in any browser and even if it was it's specified that it can only be used from worker threads, so this doesn't seem like it'd help anyway. Although, reading some other things it sounds like there's a way to pull of this trick using recursion... I'm throwing together some test code now, I'll post it if/when I get it working, seems like a very interesting way to get around any such situation generically.
编辑2:嗯,似乎没有帮助......似乎API的同步版本没有在任何浏览器中实现,即使它被指定只能从工作线程使用,所以这不会似乎无论如何它都会有所帮助。虽然,阅读其他一些东西,听起来好像有一种方法可以使用递归来实现这个技巧......我现在正在整理一些测试代码,如果/当我开始工作时,我会发布它,看起来很有趣一般绕过任何此类情况的方法。
edit3: As per my comments below, there's really no way to do exactly what I wanted. The solution I'm going with to solve my immediate problem is to simply not allow usage of web SQL for data storage. It's not the ideal solution, but as that spec is in flux and not widely implemented anyway it's not the end of the world... hopefully when its properly supported the synchronous version will be available and I can plug in a new provider for it and be good to go. Generically though, there doesn't appear to be any way to pull of this miracle... confirms what I expected was the case, but wish I was wrong this one time :)
编辑 3:根据我下面的评论,真的没有办法完全按照我的意愿去做。我要解决的直接问题的解决方案是根本不允许使用 Web SQL 进行数据存储。这不是理想的解决方案,但是由于该规范不断变化并且没有广泛实施,所以这并不是世界末日......希望当它得到适当支持时,同步版本将可用,我可以为它插入一个新的提供者,祝你好运。不过,一般来说,似乎没有任何方法可以实现这个奇迹……证实了我的预期,但希望这一次我是错的 :)
采纳答案by jfriend00
No, you can't block until the asynch call finishes. It's that simple.
不,在异步调用完成之前你不能阻塞。就这么简单。
It sounds like you may already know this, but if you want to use asynchronous ajax calls, then you have to restructure the way your code is used. You cannot just have a .get() method that makes an asynchronous ajax call, blocks until it's complete and returns the result. The design pattern most commonly used in these cases (look at all of Google's javascript APIs that do networking, for example) is to have the caller pass you a completion function. The call to .get()
will start the asynchronous operation and then return immediately. When the operation completes, the completion function will be called. The caller must structure their code accordingly.
听起来您可能已经知道这一点,但是如果您想使用异步 ajax 调用,那么您必须重构代码的使用方式。你不能只有一个 .get() 方法来进行异步 ajax 调用,阻塞直到它完成并返回结果。在这些情况下最常用的设计模式(例如,查看所有 Google 的 javascript API 进行网络连接)是让调用者向您传递一个完成函数。调用.get()
将启动异步操作,然后立即返回。当操作完成时,将调用完成函数。调用者必须相应地构建他们的代码。
You simply cannot write straight, sequential procedural javascript code when using asynchronous networking like:
使用异步网络时,您根本无法编写直接的、顺序的程序 javascript 代码,例如:
var result = abc.get()
document.write(result);
The most common design pattern is like this:
最常见的设计模式是这样的:
abc.get(function(result) {
document.write(result);
});
If your problem is several calling layers deep, then callbacks can be passed along to different levels and invoked when needed.
如果您的问题涉及多个调用层,则可以将回调传递到不同级别并在需要时调用。
FYI, newer browsers support the concept of promises which can then be used with async
and await
to write code that might look like this:
仅供参考,较新的浏览器支持承诺的概念,然后可以使用async
并await
编写可能如下所示的代码:
async function someFunc() {
let result = await abc.get()
document.write(result);
}
This is still asynchronous. It is still non-blocking. abc.get()
must return a promise that resolves to the value result
. This code must be inside a function that is declared async
and other code outside this function will continue to run (that's what makes this non-blocking). But, you get to write code that "looks" more like blocking code when local to the specific function it's contained within.
这仍然是异步的。它仍然是非阻塞的。 abc.get()
必须返回一个解析为 value 的承诺result
。此代码必须在已声明的函数内,async
并且此函数外的其他代码将继续运行(这就是非阻塞的原因)。但是,您可以编写“看起来”更像是阻塞代码的代码,当它位于包含其中的特定函数的本地时。
回答by goat
spawn a webworker thread to do the async operation for you. pass it info it needs to do the task plus a unique id. the trick is to have it send the result to a webserver when it finishes.
生成一个 webworker 线程来为您执行异步操作。传递它需要完成任务的信息以及唯一的ID。诀窍是让它在完成后将结果发送到网络服务器。
meanwhile...the function which spawned the webworker sends an ajax request to the same webserver use the synchronous flag of the xmlhttprequest object(yes, it has a synchronous option). since it will block until the http request is complete, you can just have your webserver script poll the database for updates or whatever until the result has been sent to it.
同时...产生网络工作者的函数使用 xmlhttprequest 对象的同步标志(是的,它有一个同步选项)向同一个网络服务器发送一个ajax请求。因为它会阻塞直到 http 请求完成,所以你可以让你的网络服务器脚本轮询数据库以获取更新或其他内容,直到结果发送给它。
ugly, i know. but it would block without hogging cpu :D
丑,我知道。但它会在不占用 CPU 的情况下阻塞:D
basically
基本上
function get(...) {
spawnWebworker(...);
var xhr = sendSynchronousXHR(...);
return xhr.responseTEXT;
}
回答by RightSaidFred
Why not just have the original caller pass in a callback of its own to get()
? This callback would contain the code that relies on the response.
为什么不让原始调用者将自己的回调传递给get()
?此回调将包含依赖于响应的代码。
The get()
method will forward the callback to providerGet()
, which would then invoke it when it invokes its own callback.
该get()
方法将回调转发到providerGet()
,然后当它调用自己的回调时调用它。
The result of the fetch would be passed to the original caller's callback.
获取的结果将传递给原始调用者的回调。
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 );
});
回答by Adam Rackis
When your async method starts, I would open some sort of modal dialog (that the user cannot close) telling them that the request is in process. When the request finishes, close the modal in your callback.
当您的异步方法启动时,我会打开某种模式对话框(用户无法关闭),告诉他们请求正在处理中。请求完成后,关闭回调中的模式。
One possible way to do this is with jqModal, but that would require you to load jQuery into your project. I'm not sure if that's an option for you or not.
一种可能的方法是使用jqModal,但这需要您将 jQuery 加载到您的项目中。我不确定这是否适合您。
回答by Fabio Beltramini
This is ugly, but anyway I think the question is kindof implying an ugly solution is desired...
这很丑陋,但无论如何我认为这个问题有点暗示需要一个丑陋的解决方案......
- In your get function, serialize your query into a string.
- Open an iframe, passing (A) this serialized query and (B) a random number in querystring to this iframe
- Your iframe has some javascript code that reads the SQL query and number from its own querystring
- Your iframe asynchronouslybegins running the query.
- When your iframe query is asynchronously finished, it sends it, along with the random number to a server of yours, say to /write.php?rand=###&reslt="blahblahblah"
- Write.php saves this info somewhere
- Back in your main script, after creating and loading the iframe, you create a synchronousAJAX request to your server, say to /read.php?rand=####
- /read.php blocks until the written info is available, then returns it to your main page
- 在您的 get 函数中,将您的查询序列化为一个字符串。
- 打开一个 iframe,将 (A) 这个序列化的查询和 (B) 查询字符串中的一个随机数传递给这个 iframe
- 您的 iframe 有一些 javascript 代码,可以从它自己的查询字符串中读取 SQL 查询和编号
- 您的 iframe异步开始运行查询。
- 当您的 iframe 查询异步完成时,它会将它与随机数一起发送到您的服务器,例如 /write.php?rand=###&reslt="blahblahblah"
- Write.php 将此信息保存在某处
- 回到你的主脚本,在创建和加载 iframe 之后,你创建一个到你的服务器的同步AJAX 请求,比如 /read.php?rand=####
- /read.php 阻塞直到写入的信息可用,然后将其返回到您的主页
Alternately, to avoid sending the data over the network, you could instead have your iframe encode the result into a canvas-generated image that the browser caches (similar to the approach that Zombie cookie reportedly used). Then your blocking script would try to continually load this image over and over again (with some small network-generated delay on each request) until the cached version is available, which you could recognize via some flag that you've set to indicate it's done.
或者,为了避免通过网络发送数据,您可以让 iframe 将结果编码为浏览器缓存的画布生成图像(类似于 Zombie cookie 据称使用的方法)。然后你的阻塞脚本会尝试一遍又一遍地不断加载这个图像(每个请求都有一些小的网络生成延迟),直到缓存版本可用,你可以通过一些你设置的标志来识别它以指示它已完成.