简体   繁体   English

如何调用异步JavaScript函数并阻止原始调用者

[英]How to call an asynchronous JavaScript function and block the original caller

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. 我有一个有get()方法的类...这个方法被调用来获取存储的用户首选项......它所做的是调用某个底层提供程序来实际获取数据...现在写入,它正在调用会话cookie的提供者...所以,get()调用providerGet()让我们说,providerGet()返回一个值,get()将它传递给调用者。 The caller expects a response before it continues its work obviously. 调用者希望在它继续工作之前做出响应。

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? 那么,问题是,是否有一种方法可以基本上“阻止”从providerGet()返回,直到异步调用返回? 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. 请注意,出于我的目的,我并不关心这可能带来的性能影响,我只想弄清楚如何使其工作。

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. edit2:好吧,似乎没有任何帮助......似乎API的同步版本没有在任何浏览器中实现,即使它是指定它只能从工作线程使用,所以这不无论如何它似乎都有所帮助。 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. 虽然,读一些其他的东西,听起来有一种方法来使用递归来拉动这个技巧...我现在正在抛出一些测试代码,如果/当我让它工作时我会发布它,看起来像一个非常有趣的一般地解决任何这种情况的方法。

edit3: As per my comments below, there's really no way to do exactly what I wanted. 编辑3:根据我下面的评论,我真的没办法完全按照自己的意愿行事。 The solution I'm going with to solve my immediate problem is to simply not allow usage of web SQL for data storage. 我要解决我当前问题的解决方案是不允许使用Web SQL进行数据存储。 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 :) 一般来说,似乎没有任何方式可以拉动这个奇迹...确认我的预期是这样的,但希望我错了这一次:)

spawn a webworker thread to do the async operation for you. 产生一个webworker线程来为你做异步操作。 pass it info it needs to do the task plus a unique id. 传递它需要执行任务加上唯一ID的信息。 the trick is to have it send the result to a webserver when it finishes. 诀窍是让它在结束时将结果发送到网络服务器。

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). 同时......产生webworker的函数向同一个webserver发送一个ajax请求,使用xmlhttprequest对象的同步标志(是的,它有一个同步选项)。 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. 因为它将阻止直到http请求完成,你可以让你的webserver脚本轮询数据库以获取更新或其他任何内容,直到结果发送给它。

ugly, i know. 丑陋,我知道。 but it would block without hogging cpu :D 但它会阻塞而不会占用cpu:D

basically 基本上

function get(...) {
    spawnWebworker(...);
    var xhr = sendSynchronousXHR(...);
    return xhr.responseTEXT;
}

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. 听起来你可能已经知道这一点,但如果你想使用异步ajax调用,那么你必须重新调整代码的使用方式。 You cannot just have a .get() method that makes an asynchronous ajax call, blocks until it's complete and returns the result. 你不能只有一个.get()方法来进行异步ajax调用,阻塞直到它完成并返回结果。 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. 在这些情况下最常用的设计模式(例如,查看所有进行网络连接的Google的javascript API)是让调用者为您传递完成功能。 The call to .get() will start the asynchronous operation and then return immediately. .get()的调用将启动异步操作,然后立即返回。 When the operation completes, the completion function will be called. 操作完成后,将调用完成功能。 The caller must structure their code accordingly. 调用者必须相应地构造其代码。

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: 仅供参考,较新的浏览器支持promises的概念,然后可以与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 . abc.get()必须返回一个解析为值result的promise。 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). 此代码必须位于声明为async的函数内,此函数外的其他代码将继续运行(这就是使此非阻塞的原因)。 But, you get to write code that "looks" more like blocking code when local to the specific function it's contained within. 但是,您可以编写“看起来”更像阻塞代码的代码,当它包含在其中的特定函数的本地时。

Why not just have the original caller pass in a callback of its own to get() ? 为什么不让原始调用者在其自己的回调中传递给get() This callback would contain the code that relies on the response. 此回调将包含依赖于响应的代码。

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

This is ugly, but anyway I think the question is kindof implying an ugly solution is desired... 这很难看,但无论如何,我认为这个问题有点暗示需要一个丑陋的解决方案......

  1. In your get function, serialize your query into a string. 在get函数中,将查询序列化为字符串。
  2. Open an iframe, passing (A) this serialized query and (B) a random number in querystring to this iframe 打开iframe,将(A)此序列化查询和(B)查询字符串中的随机数传递给此iframe
    • Your iframe has some javascript code that reads the SQL query and number from its own querystring 你的iframe有一些javascript代码,它从自己的查询字符串中读取SQL查询和数字
    • Your iframe asynchronously begins running the query. 您的iframe 异步开始运行查询。
    • 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" 当您的iframe查询异步完成时,它会将它与随机数一起发送到您的服务器,对/write.php?rand=###&reslt="blahblahblah“
    • Write.php saves this info somewhere Write.php将此信息保存在某处
  3. Back in your main script, after creating and loading the iframe, you create a synchronous AJAX request to your server, say to /read.php?rand=#### 回到主脚本,在创建并加载iframe之后,为服务器创建一个同步 AJAX请求,比如/read.php?rand=####
  4. /read.php blocks until the written info is available, then returns it to your main page /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). 或者,为了避免通过网络发送数据,您可以让iframe将结果编码为浏览器缓存的画布生成的图像(类似于Zombie cookie据称使用的方法)。 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. 然后你的阻止脚本会尝试不断地反复加载这个图像(每个请求都有一些小的网络生成的延迟),直到缓存的版本可用,你可以通过你设置的一些标志来识别它已经完成。

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 . 一种可能的方法是使用jqModal但这需要您将jQuery加载到项目中 I'm not sure if that's an option for you or not. 我不确定这是否是你的选择。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM