【问题标题】:How to call an asynchronous JavaScript function and block the original caller如何调用异步 JavaScript 函数并阻止原始调用者
【发布时间】:2011-12-09 16:03:31
【问题描述】:

我遇到了一个有趣的情况,我通常聪明的头脑无法想出解决方案:) 这是情况......

我有一个具有 get() 方法的类...调用此方法以获取存储的用户首选项...它所做的是调用一些底层提供程序以实际获取数据...如现在所写,它正在调用一个与 cookie 对话的提供者……所以, get() 调用 providerGet() 假设,providerGet() 返回一个值,而 get() 将其传递给调用者。调用者在明显继续其工作之前期望得到响应。

这是棘手的部分......我现在正在尝试实现一个本质上是异步的提供程序(在这种情况下使用本地存储)......所以,providerGet() 将立即返回,已调度调用本地存储将在一段时间后调用传递给它的回调函数......但是,由于 providerGet() 已经返回,get() 现在通过对原始调用的扩展也返回了,它显然没有返回实际检索到的数据。

所以,问题只是有没有办法从本质上“阻止” providerGet() 的返回,直到异步调用返回?请注意,出于我的目的,我并不关心这可能对性能产生的影响,我只是想弄清楚如何让它发挥作用。

我不认为有办法,当然我知道我无法想出它......所以我想把它扔掉,看看其他人能想出什么:)

编辑:我现在才知道问题的核心,即 web sql API 是异步的,可能有一个解决方案......原来也有一个同步版本的 API,我没有'没有意识到......我现在正在阅读文档以了解如何使用它,但这会很好地解决问题,因为providerGet()被异步编写的唯一原因是允许该提供者......代码get() 是我自己在各种存储提供程序(cookies、web sql、localStorage 等)之上的抽象层的一部分,因此必须以最低的公分母获胜,这意味着如果一个是异步的,它们都必须是异步的。 . 唯一的一个是 web sql ......所以如果有办法同步地做到这一点,我的观点就变得毫无意义(但我认为这仍然是一个有趣的问题)

edit2:啊,好吧,似乎没有帮助......似乎API的同步版本没有在任何浏览器中实现,即使它指定它只能从工作线程中使用,所以这个无论如何,它似乎没有帮助。虽然,读一些其他的东西,听起来好像有一种方法可以使用递归来解决这个问题......我现在正在整理一些测试代码,如果/当我让它工作时,我会发布它,看起来很有趣一般地解决任何此类情况的方法。

edit3:根据我下面的 cmets,真的没有办法完全按照我的意愿去做。我要解决我的直接问题的解决方案是根本不允许使用 Web SQL 进行数据存储。这不是理想的解决方案,但由于该规范在不断变化并且没有被广泛实施,所以它不是世界末日......希望当它得到适当支持时,同步版本将可用,我可以为它插入一个新的提供程序并且走吧。不过一般来说,似乎没有任何方法可以解决这个奇迹......证实了我的预期,但希望我这次错了:)

【问题讨论】:

  • 不,真的没有办法做到这一点。真正的解决方案是反转您的 API,以便将您自己的“get()”传递给回调。
  • 如果您无法更改 API,另一种可能性是在加载时(或其他)以某种方式将本地存储中存储的任何内容直接迁移到页面中。如果没有太多存储,那么这将允许您在需要时从“providerGet()”实现直接访问它。
  • 看看这个项目。它可能就在你的小巷里......github.com/JeffreyZhao/jscex
  • 使用外部状态和进度检查,你有时可以抛出 setTimeout(arguments.callee.bind(this,arguments,100); 稍后再接,但你的老板不会喜欢那种代码。 ..

标签: javascript blocking


【解决方案1】:

生成一个 webworker 线程来为您执行异步操作。 传递它执行任务所需的信息以及唯一的 ID。 诀窍是让它在完成后将结果发送到网络服务器。

同时...生成 webworker 的函数向同一个 webserver 发送 ajax 请求 使用 xmlhttprequest 对象的同步标志(是的,它有一个同步选项)。因为它会阻塞直到 http 请求完成,所以您可以让您的网络服务器脚本轮询数据库以获取更新或其他任何内容,直到结果发送给它。

丑,我知道。但它会在不占用 CPU 的情况下阻塞:D

基本上

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

【讨论】:

  • 我喜欢很棒的 hack! +1
  • 有没有办法在不阻塞 UI 的情况下利用这种方法?我正在拼命寻找removal of the showModalDialog API 的解决方法。
  • @BrettPostin 但现在我们有了 Dialog API,不是吗?
  • @AnthonyHunt 我不相信 Dialog API 会像 alert/confirm/showModalDialog 那样阻塞。
【解决方案2】:

不,在异步调用完成之前您不能阻塞。就这么简单。

听起来您可能已经知道这一点,但是如果您想使用异步 ajax 调用,那么您必须重新构建代码的使用方式。你不能只有一个 .get() 方法来进行异步 ajax 调用,阻塞直到它完成并返回结果。在这些情况下最常用的设计模式(例如,查看 Google 的所有用于联网的 javascript API)是让调用者向您传递一个完成函数。对.get() 的调用将启动异步操作,然后立即返回。当操作完成时,将调用完成函数。调用者必须相应地构造他们的代码。

在使用异步网络时,您根本无法编写直接的、顺序的 JavaScript 代码,例如:

var result = abc.get()
document.write(result);

最常见的设计模式是这样的:

abc.get(function(result) {
    document.write(result);
});

如果您的问题涉及多个调用层,则可以将回调传递到不同的级别并在需要时调用。


仅供参考,较新的浏览器支持 Promise 的概念,然后可以将其与 asyncawait 一起使用来编写如下所示的代码:

async function someFunc() {
    let result = await abc.get()
    document.write(result);
}

这仍然是异步的。它仍然是非阻塞的。 abc.get() 必须返回一个解析为值 result 的承诺。此代码必须在声明为async 的函数内,并且此函数外的其他代码将继续运行(这就是使其非阻塞的原因)。但是,您可以编写“看起来”更像阻塞代码的代码,当它位于特定函数的本地时。

【讨论】:

  • 是的,你是完全正确的,是的,我非常了解这一切 :) 我希望有一个聪明的解决方案我还没有想出来,但没有。我的递归想法也行不通……如果 JavaScript 不是单线程的,理论上它会起作用……但是一旦你进入一个递归循环,一遍又一遍地调用 getProvider(),这就是想法要求,您的异步功能将永远无法有效完成(实际上,由于递归过多,您每次在我的测试中都会使浏览器崩溃)。所以,是的,我搞砸了是底线:(
  • @Frank W. Zammetti 您可以让递归循环在检查之间异步休眠以让其他函数完成,但您可能不应该这样做。
【解决方案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 );
});

【讨论】:

  • 不幸的是,调用者是 UI 库的一部分,所以这样的改变是不可行的...... get() 也被称为 UI 小部件渲染的一部分(它检索宽度用户可以更改的网格中的列)。
  • @FrankW.Zammetti:您的新代码是按设计还是按要求异步?换句话说,是否可以同步进行?我想我不明白你为什么希望它是异步的,但你又希望它同时阻塞。
  • 是的,它的设计是同步的……问题在于,providerGet() 所属的底层提供程序可以是同步的,也可以不是同步的……get() 方法是其中的一部分上面的抽象层......复杂之处在于 get() 的调用者是一个 UI 库,它在构造 UI 时调用(例如,在构造网格小部件时,它需要检索列的宽度,这每个用户都可以更改和存储)...但是,看起来有一个很好的递归解决方案,我正在努力将一些示例代码放在一起,并在它工作时发布。
  • @FrankW.Zammetti:收到通知后给我发送通知。我想看看你的意思。如果我理解正确,providerGet() can 是同步的,但它的调用者正在使其 async (或者至少不给出偏好,默认为 异步)。听起来对吗?
【解决方案4】:

当您的异步方法启动时,我会打开某种模式对话框(用户无法关闭),告诉他们请求正在处理中。请求完成后,关闭回调中的模式。

一种可能的方法是使用jqModal但这需要您将 jQuery 加载到您的项目中。我不确定这是否适合您。

【讨论】:

  • @user886931 - 加载 jQuery just 以显示模式对话框对我来说有点多 :)
  • 有趣的建议...在用户可以知道发生了什么的情况下是可行的,但在这种情况下它不起作用:get() 方法的调用者实际上是 UI工具包代码及其获取的是用户在呈现 UI 时可能已修改的网格中列的宽度。
  • 这个问题没有提到关于用户界面或用户的任何内容。更多的是关于代码。 modal 只不过是一个覆盖所有其他元素的 div,你不需要 jQuery,它是纯 CSS。
  • @Frank - 然后你需要稍微修改一下你的 api,并提供你自己的回调,就像上面说的 RightSaidfred。
  • @Interstellar_Coder - 我以为他想阻止程序并且在请求完成之前不让用户做任何事情。而且您 100% 正确,您不需要 jQuery 作为模态。但如果他已经在使用 jQuery,我认为 jqModal 将是实现这一目标的简单方法。只是想尽可能多地提供信息,以便他自己决定:)
【解决方案5】:

这很丑陋,但无论如何我认为这个问题有点暗示需要一个丑陋的解决方案......

  1. 在 get 函数中,将查询序列化为字符串。
  2. 打开一个 iframe,将 (A) 此序列化查询和 (B) 查询字符串中的随机数传递给此 iframe
    • 您的 iframe 有一些 JavaScript 代码,可以从自己的查询字符串中读取 SQL 查询和编号
    • 您的 iframe 异步开始运行查询。
    • 当您的 iframe 查询异步完成时,它会将其连同随机数一起发送到您的服务器,例如 /write.php?rand=###&reslt="blahblahblah"
    • Write.php 将此信息保存在某处
  3. 回到您的主脚本,在创建和加载 iframe 之后,您创建一个 同步 AJAX 请求到您的服务器,例如 /read.php?rand=####
  4. /read.php 阻塞,直到写入的信息可用,然后将其返回到您的主页

或者,为了避免通过网络发送数据,您可以让 iframe 将结果编码为浏览器缓存的画布生成的图像(类似于 Zombie cookie 据报道使用的方法)。然后,您的阻止脚本将尝试一遍又一遍地不断加载此图像(每个请求都有一些小的网络生成延迟),直到缓存版本可用,您可以通过您设置的一些标志来识别它已完成.

【讨论】:

  • 糟糕,我错过了 Chris 的回答,这基本上是一样的...... :-/
猜你喜欢
  • 2011-05-19
  • 1970-01-01
  • 2013-07-28
  • 2012-02-25
  • 1970-01-01
  • 1970-01-01
  • 2010-11-08
  • 2022-12-04
  • 1970-01-01
相关资源
最近更新 更多