【问题标题】:Why are Q.js promises asynchronous after they have been resolved?为什么 Q.js 的 Promise 在解决后是异步的?
【发布时间】:2014-09-12 22:01:07
【问题描述】:

如果我有以下情况:

var deferred = Q.defer();

deferred.resolve();

var a = deferred.promise.then(function() {
    console.log(1);    
});

console.log(2); 

...为什么我在控制台中看到 2,然后是 1?

根据 Promises 规范,我知道这个输出是正确的,它说在下一个滴答时调用函数(例如 setTimeout()),即使它已经解决了,但我不明白为什么。

假设所有的承诺都已解决,我希望有代码在一系列承诺上同步调用then

我真正的用例是我正在尝试使用 Angular 的实现 $q,并且我希望所有的 then 回调在同一个 $digest 循环中执行,这样我就不会得到不必要的后续$digest 个周期。

【问题讨论】:

标签: javascript angularjs promise


【解决方案1】:

这是基于一组意见和假设的设计错误。它之所以被锁定,是因为它在委员会设计过程中没有经过全面技术验证就匆匆推出,这也有许多供应商已经实施自己的产品的背压,同样的错误使其难以回溯。

一旦 JS 的标准发布到网络上,它就可以被撤销,即使它被打破的想法是网页不应该被打破。如果今天有人写了一个页面,然后死了,五年后应该可以在你的浏览器中查看它。如果您在浏览网页时不断碰到无法使用您的浏览器的页面,那将是非常有问题的。

在非常简单的用例中,它不会造成太大的伤害,并且消除了关于某些东西是否可能是异步的混淆。

对于越来越重要的用例,它会造成越来越大的伤害并增加混乱。最初,它似乎更容易推理您的代码,但它牺牲了不那么琐碎的用法,换取了最不琐碎的用法。

如果代码在下一个滴答中不运行根本不需要异步的东西,那么总体上推理您的代码要容易得多。这是一个选择破坏二级语言以迎合一级用户的情况,以牺牲二级及以上用户为代价,而不是努力帮助一级用户提升到二级用户。这是一个居高临下或悲观的设计决定。

有一个中间解决方案可以运行每个任务,就好像它在当前代码运行完成之后运行一样,但事情是按照正确的顺序安排的。这还没有实施,也有争议,因为妥协并不总是产生最好的解决方案。这种折衷会导致直接返回调用堆栈的性能问题。

promise 的工作方式意味着前向调用堆栈是深度优先并运行到完成(隔离),但返回调用堆栈是广度优先并与其他返回调用堆栈交错运行。这是两种截然不同的概念和行为。传统上,回调都以相同的方式运行。

这也意味着你不能天真地用承诺或任何基于承诺的东西替换回调。回调为您提供了更多的选项,这些选项被承诺带走。如果您将回调替换为 Promise 而没有考虑到这种差异,您可能会创建具有稳定性问题和潜在安全问题的代码,因为这种无序事件的行为可能会导致当前代码流意外跳轨。

您不能依赖订单,这意味着在某些情况下,如果您要求很多东西,当您取回它们时,您必须仔细检查它们是否是您要求的东西,因为您不需要用回调做到这一点。您可能还需要缓冲和重新排序不需要对回调执行的事件。如果您不小心隔离正在运行的两件事,它也会使基准测试变得不可靠。

这也可能会造成严重的性能瓶颈,您无法始终轻松避免。如果你使用 promises 一次从单个事件返回到迭代器的一百个返回结果,每个返回调用需要一秒钟,并且它们的 promise 解析深度是 2,它们将全部分成两半,前半部分全部运行,然后下半场。这意味着任何事情都需要 50.5 秒才能完成,而 50 秒后的回调已经完成了一半。如果任务的结果被传递给另一个外部服务进行处理,那么它会让该服务闲置 50 秒,而它本来可以处理您的结果。当您希望在承受负载的服务下同时获得低延迟和高吞吐量时,这使得承诺变得很糟糕,这表明了设计的弱点。

不能天真地用 Promise 替换回调是这个设计错误的最具破坏性的后果之一,它也被带到 async/await 中。如果你想转换一个回调库,你不能简单地改变语法,你必须仔细检查每一点的语义。

没有解决此问题的计划。您可以创建自己的 Promise,并且生成器可用于提供与 async/await 相同的语法,但具有与回调相同的可预测和高性能行为。但是,与仍然依赖原生 Promise 的其他库配合使用时,您可能会遇到问题。

【讨论】:

  • 投了反对票,因为 a) 我不同意内容,b) 它作为咆哮出现,但您能否详细说明您的意思是“尚未实施,事情是以正确的顺序安排”和“您不能依赖顺序”?也许有例子?
  • 内容基于对 ES 规范的研究和测试。不幸的是,这一切都是真实的。你提出了一个观点。我没有用 Q 测试它。我只是假设它们符合相同的互操作性标准。
  • 我的观点不是关于 Q 与原生承诺,我的观点是你的帖子真的不清楚你在哪些代码上遇到了性能/调度问题。
  • 没有。问题的答案是“它这样做是因为它是这样指定的”,并且可能是“它是这样设计的,因为……”。相比之下,您的帖子仅声明“我非常不同意它的工作原理,并认为它是一个糟糕的设计”(轻描淡写)。
  • 问题是,我在完成表演部分时遇到了麻烦。 “承诺解决深度为两个”是什么意思?如果 Promise 与适当的非阻塞代码一起用于并发任务,那么 Promise 的调度时间怎么会是 50 秒左右?
【解决方案2】:

答案是一致性。

在实际代码中,您没有在创建时总是立即解决的承诺,它们将毫无意义。因此,您承诺有时可能会立即得到解决。

在这种情况下,您不希望有不同的流程。您希望始终保持相同的、可预测的流量。所以你希望下一个函数总是在下一次滴答时被调用。

当你不需要时不要使用承诺。

【讨论】:

  • 我相信的技术术语是“不要发布 Zalgo”,方法必须始终是异步或同步的,否则你会遇到非常奇怪的竞争条件,这会让你哭泣.
  • 感谢您的回答。也许我不想要一个承诺。我想要一种请求 HTTP 资源的简洁方式,将其提供给初始调用者,然后将其缓存以提供给后续调用者。我使用了一个执行 HTTP 请求的 Promise,并且我正在存储 Promise 对象,以便后续调用者可以将回调传递给“then”,并让它尽快调用,最好在下一个刻度之前调用(我还有其他行为称为在下一个滴答声中,我希望在其他行为运行之前满足此代码)。你认为我对承诺的使用不合适吗?再次感谢。
  • @MariuszNowak 在我看来,这是一个非常糟糕的主意。极少数情况下它可能会带来更好的性能并不能弥补分析代码的困难和它会导致的细微错误。
  • @MariuszNowak 加油... 几年 33 期不是很多用户... 我可以在 .done 上了解您的立场(我仍然认为这是一个多余的工件做一些库或本机承诺应该做的事情的幼稚实现) - 但是发布 Zalgo?进行这种同步调度是一个可怕的想法,jQuery 的承诺在这方面是臭名昭著的。我个人不得不用 jQuery 承诺(表现出这种行为)来处理几种这样的情况......这绝对不是“非问题”。
猜你喜欢
  • 2016-11-08
  • 1970-01-01
  • 2015-03-30
  • 2016-12-26
  • 2019-03-25
  • 1970-01-01
  • 2013-09-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多