Twisted的延迟是否与JavaScript中的Promise相同?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Twisted的延迟是否与JavaScript中的Promise相同?相关的知识,希望对你有一定的参考价值。
我开始在需要异步编程的项目中使用Twisted,而且文档非常好。
所以我的问题是,在javascript中延迟与扭曲是一样的承诺吗?如果没有,有什么区别?
你的问题的答案是肯定的和否定的,这取决于你问的原因。
Yes:
Twisted Deferred
和Javascript Promise
都实现了一种机制,用于排队同步代码块,以便在与其他同步代码块分离的同时以给定顺序运行。
No:
所以Javascript的Promise
实际上更类似于Python的Future
,而通风的仙女解释这个就是讨论Promise
和Resolver
被组合起来制作一个Deferred
,并声明这会影响你可以用回调做什么。
这一切都非常好,因为它是准确的,但它并没有真正使任何更清楚的东西,并且没有键入成千上万的单词,我几乎可以保证犯错误,我可能更好地引用一个知道一个人的人关于Python的一点点。
Guido van Rossum on Deferreds:
这是我尝试向没有Twisted经验的高级Python用户解释Deferred的重要想法(并且有很多这些想法)。我还假设你以前考虑过异步调用。只是为了惹恼Glyph,我正在使用一个五星系统来表明思想的重要性,其中一颗星是“好主意但非常明显”,而五星则是“精彩”。
我展示了很多代码片段,因为有些想法最好用这种方式表达 - 但我故意遗漏了很多细节,有时我会显示有错误的代码,如果修复它们会减少对代码背后的想法的理解。 (我会指出这样的错误。)我正在使用Python 3。
专门针对字形的注释:(a)考虑一下博客文章的草稿。我非常乐意采取纠正措施和改进建议。 (b)这并不意味着我要将Tulip改为更像延迟的模型;但这是针对不同的主题。
想法1:返回一个特殊对象,而不是采用回调参数
在设计以异步方式生成结果的API时,您会发现需要一个用于回调的系统。通常,我想到的第一个设计是传递一个回调函数,该函数将在异步操作完成时调用。我甚至已经看过设计,如果你没有通过回调,那么操作是同步的 - 这已经足够糟糕,我会给它零星。但即使是一星级版本也会污染所有带有额外参数的API,这些参数必须经常传递。扭曲的第一个重要的想法是,最好返回一个特殊的对象,调用者可以在接收后添加回调。我给了这三颗星,因为它发出了许多其他好主意。它当然类似于在许多语言和库中发现的期货和承诺的基础,例如, Python的concurrent.futures(PEP 3148,紧跟Java Futures,两者都是针对线程世界)和现在的Tulip(PEP 3156,使用类似的设计适用于无线程异步操作)。
想法2:将回调结果传递给回调
我认为最好先显示一些代码:
class Deferred: def __init__(self): self.callbacks = [] def addCallback(self, callback): self.callbacks.append(callback) # Bug here def callback(self, result): for cb in self.callbacks: result = cb(result)
最有趣的位是最后两行:每个回调的结果传递给下一行。这与concurrent.futures和Tulip中的工作方式不同,其中结果(一旦设置)被固定为Future的属性。每次回调都可以修改结果。
当一个返回Deferred的函数调用另一个函数并转换其结果时,这将启用一个新模式,这就是获得三个星的想法。例如,假设我们有一个异步函数来读取一组书签,我们想编写一个异步函数来调用它,然后对书签进行排序。而不是发明一种机制,一个异步函数可以等待另一个(我们将在以后再做:-),第二个异步函数可以简单地向第一个返回的Deferred添加一个新的回调:
def read_bookmarks_sorted(): d = read_bookmarks() d.addCallback(sorted) return d
此函数返回的Deferred表示已排序的书签列表。如果其调用者想要打印这些书签,则必须添加另一个回调:
d = read_bookmarks_sorted() d.addCallback(print)
在Futures表示异步结果的世界中,同样的示例需要两个单独的Futures:一个由read_bookmarks()返回,表示未排序的列表,另一个Future由read_bookmarks_sorted()返回,表示排序列表。
在这个版本的类中有一个非明显的错误:如果在Deferred已经触发后调用了addCallback()(即调用了它的callback()方法),那么addCallback()添加的回调将永远不会被调用。解决这个问题很容易,但很乏味,你可以在Twisted源代码中查找。我将通过连续的例子来传达这个错误 - 只是假装你生活在一个结果从未准备好的世界里。这个设计也有其他问题,但我宁愿调用解决方案改进而不是错误修正。
旁白:Twisted的术语选择不佳
我不知道为什么,但是,从项目自己的名字开始,Twisted经常以错误的方式选择其名称。例如,我真的很喜欢类名应该是名词的指南。但是“延迟”是一个形容词,而不仅仅是形容词,它是动词的过去分词(并且过于长篇:-)。为什么它在名为twisted.internet的模块中?
然后是'回调',它用于两个相关但不同的目的:它是用于在结果准备好时调用的函数的首选术语,但它也是你调用“fire”的方法的名称“延期,即设定(初始)结果。
不要让我开始使用'errback'的neologism / portmanteau,它引导我们......
想法3:集成错误处理
这个想法只有两颗星(我肯定会让许多Twisted粉丝失望),因为它让我很困惑。我还注意到Twisted文档在解释它是如何工作方面遇到了一些麻烦 - 在这种情况下,特别是我发现阅读代码比文档更有帮助。
基本的想法很简单:如果用结果解雇延期的承诺无法实现怎么办?当我们写作
d = pod_bay_doors.open() d.addCallback(lambda _: pod.launch())
怎么HAL 9000应该说“对不起,戴夫。我怕我做不到”?
即使我们不关心这个答案,如果其中一个回调引发异常,我们该怎么办呢?
Twisted的解决方案是将每个回调分为回调和'错误'。但这并非全部 - 为了处理回调引发的异常,它还引入了一个新类,'失败'。我实际上想首先介绍后者,而不引入错误:
class Failure: def __init__(self): self.exception = sys.exc_info()[1]
(顺便说一句,伟大的班级名称。我的意思是,我不是在讽刺。)
现在我们可以重写callback()方法,如下所示:
def callback(self, result): for cb in self.callbacks: try: result = cb(result) except: result = Failure()
这本身就是两颗星;回调可以使用isinstance(结果,失败)来告诉除了失败之外的常规结果。
顺便说一下,在Python 3中,可以取消封装异常的单独Failure类,并使用内置的BaseException类。通过阅读代码中的注释,Twisted的Failure类主要存在,因此它可以保存sys.exc_info()返回的所有信息,即异常类/类型,异常实例和回溯,但在Python 3中,异常对象已经存在参考traceback。有一些调试的东西,Twisted的Failure类做了哪些标准异常没有,但是,我认为引入一个单独的类的大多数原因已得到解决。
但是,我们不要忘记错误。我们将回调列表更改为回调函数对的列表,然后再次重写callback()方法,如下所示:
def callback(self, result): for (cb, eb) in self.callbacks: if isinstance(result, Failure): cb = eb # Use errback try: result = cb(result) except: result = Failure()
为方便起见,我们还添加了一个errback()方法:
def errback(self, fail=None): if fail is None: fail = Failure() self.callback(fail)
(真正的errback()函数有一些特殊情况,它可以使用异常或Failure作为参数调用,而Failure类采用可选的异常参数来阻止它使用sys.exc_info()。但是没有这是必不可少的,它使代码片段更复杂。)
为了确保self.callbacks是一个对列表,我们还必须更新addCallback()(它在Deferred被触发后调用时仍然不能正常工作):
def addCallback(self, callback, errback=None): if errback is None: errback = lambda r: r self.callbacks.append((callback, errback))
如果仅使用回调函数调用此函数,则errback将是一个将结果(即Failure实例)传递给未更改的哑元。这会保留后续错误处理程序的错误条件。为了便于添加错误处理程序而不处理常规resullt,我们添加addErrback(),如下所示:
def addErrback(self, errback): self.addCallback(lambda r: r, errback)
这里,该对的回调一半将通过(非失败)结果不变地传递给下一个回调。
如果你想要充分的动力,请阅读Twisted的Deferreds简介;我只想通过返回非故障值(包括None)来注意errback并用常规结果替换Failure。
在我继续讨论下一个想法之前,让我指出真正的Deferred类中有更多的细节。例如,您可以指定要传递给回调和errback的其他参数。但是在紧要关头你可以用lambdas做到这一点,所以我把它留下来,因为执行管理的额外代码并没有阐明基本的想法。
想法4:链接延迟
这是一个五星级的想法!有时,回调确实需要等待额外的异步事件才能产生所需的结果。例如,假设我们有两个基本的异步操作,read_bookmarks()和sync_bookmarks(),我们想要一个组合操作。如果这是同步代码,我们可以写:
def sync_and_read_bookmarks(): sync_bookmarks() return read_bookmarks()
但是如果所有操作都返回Deferreds,我们如何写这个呢?有了链接的想法,我们可以这样做:
def sync_and_read_bookmarks(): d = sync_bookmarks() d.addCallback(lambda unused_result: read_bookmarks()) return d
需要lambda是因为所有回调都是使用结果值调用的,但read_bookmarks()不带参数。
以上是关于Twisted的延迟是否与JavaScript中的Promise相同?的主要内容,如果未能解决你的问题,请参考以下文章
如何从Twisted的deferToThread API中延迟添加延迟?
使用Twisted logger时可以延迟日志文件的旋转吗?
Twisted Server通过POST接收数据流,逐字节读取request.content.read(),延迟了一个多小时