我开始在需要异步编程的项目中使用Twisted,而且文档非常好。
所以我的问题是,在Twisted中延迟是否与Javascript中的Promise相同?如果没有,有什么区别?
答案 0 :(得分:7)
您的问题的答案是是 和 否,具体取决于您提出的问题。
Twisted Deferred
和Javascript Promise
都实现了一种机制,用于对同步代码块进行排队,以便在与其他同步代码块分离的同时以给定顺序运行。
所以Javascript' s Promise
实际上更类似于Python的Future
,而通俗透彻的解释方式就是谈论Promise
和将Resolver
合并为Deferred
,并声明这会影响您对回调的处理方式。
这一切都非常好,因为它准确无误,但它并没有让任何事情变得更清晰,而且没有输入成千上万的单词,我几乎可以保证犯错误,我可能更好地引用一些对Python有所了解的人。
Guido van Rossum on Deferreds:
这是我试图解释延期的重要想法(并且有很多 对于没有Twisted经验的高级Python用户而言。 我还假设你以前考虑过异步调用。只是 为了惹恼Glyph,我正在使用一个五星系统来表明其重要性 一个想法,其中一个明星是#34;好主意,但非常明显"和5星 是"辉煌"。
我展示了很多代码片段,因为有些想法是最好的 表达了这种方式 - 但我故意遗漏了很多细节, 有时我会显示有错误的代码,如果修复它们会减少 理解代码背后的想法。 (我会指出这样的错误。) 我使用的是Python 3。
专门针对字形的注释:(a)将此视为博客的草稿 帖子。我非常乐意接受更正和建议 改进。 (b)这并不意味着我要将郁金香改为 更像延迟的模型;但这是针对不同的主题。
想法1:返回一个特殊对象,而不是采用回调参数
在设计以异步方式生成结果的API时,您会发现这一点 你需要一个回调系统。通常是第一个设计 要记住的是传入一个将在调用时调用的回调函数 异步操作完成。如果你没有,我甚至可以看到设计 传递回调操作是同步的 - 这已经足够糟糕了 我给它零星。但即使是一星级版本也会污染所有人 具有额外参数的API必须经常传递。 扭曲的第一个重要的想法就是回归特别的东西会更好 调用者在收到回调后可以添加回调的对象。一世 给这三颗星,因为从它发芽这么多的其他好处 想法。它当然类似于期货和期货的基本理念 在许多语言和库中发现的承诺,例如Python的' S concurrent.futures(PEP 3148,密切关注Java Futures,两者兼而有之) 这是一个线程世界)和现在郁金香(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中,结果(一旦设置)被修复 作为未来的一个属性。这里的结果可以由每个修改 回调。
当一个函数返回Deferred时,这将启用一个新模式 调用另一个并转换其结果,这就是收益 这个想法三颗星。例如,假设我们有一个异步函数 它读取一组书签,我们想编写一个异步函数 调用它然后对书签进行排序。而不是发明一个 一个异步函数可以等待另一个的机制(我们 无论如何都会在以后做:-),第二个异步函数可以简单地添加一个 第一个返回的Deferred的新回调:
def read_bookmarks_sorted(): d = read_bookmarks() d.addCallback(sorted) return d
此函数返回的Deferred表示已排序的列表 书签。如果其调用者想要打印这些书签,则必须添加 另一个回调:
d = read_bookmarks_sorted() d.addCallback(print)
在Futures代表异步结果的世界中,同样如此 例子需要两个单独的期货:一个返回 read_bookmarks()表示未排序的列表,以及单独的Future 由read_bookmarks_sorted()返回,表示排序列表。
这个版本的类中有一个非明显的错误:if 在Deferred已经解雇之后调用addCallback()(即它的 调用callback()方法)然后通过addCallback()添加回调 永远不会被召唤。解决这个问题很容易,但是很乏味,而且 你可以在Twisted源代码中查找它。我会带这个bug 通过连续的例子 - 假装你生活在一个世界里 结果永远不会过早准备好的地方。还有其他问题 也有这种设计,但我宁愿调用解决方案的改进 比错误修正。
除此之外:Twisted的术语选择不佳
我不知道为什么,但是,从项目自己的名字开始,Twisted 经常用错误的方式来惹我错误的选择。对于 例如,我真的很喜欢类名应该是名词的指南。 但是'延期'是形容词,而不仅仅是形容词,它是一个形容词 动词的过去分词(以及过长的分词:-)。为什么呢 它在名为twisted.internet的模块中?
然后有回调',用于两个相关但不同的 目的:它是用于函数的首选术语 在结果准备好时调用,但它也是方法的名称 你打电话给"火"延期,即设定(初始)结果。
不要让我开始使用“nerback'”的neologism / portmanteau, 这导致我们......
创意3:集成错误处理
这个想法只有两颗星(我肯定会让许多人失望 扭曲的粉丝)因为它让我很困惑。我也注意到了 扭曲的文档解释它是如何工作的 - 在这种情况下 特别是我发现阅读代码比使用代码更有帮助 文档。
基本的想法很简单:如果解雇的承诺怎么办? 延期结果无法实现?我们写的时候
d = pod_bay_doors.open() d.addCallback(lambda _: pod.launch())
HAL 9000应该怎么说"对不起,戴夫。我怕我不能 做那个" ?
即使我们不关心这个答案,如果其中一个,我们应该怎么做 回调会引发异常吗?
Twisted的解决方案是将每个回调分成回调和 一个' errback'。但这不是全部 - 为了处理异常 通过回调筹集,它还引入了一个新类,“失败”。我' d 实际上喜欢先介绍后者,而不是介绍 errbacks:
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的失败类主要存在,因此它可以容纳所有的 sys.exc_info()返回的信息,即异常类/类型, 异常实例和回溯,但在Python 3中,异常对象 已经拥有对traceback的引用。有一些调试的东西 Twisted的失败课程可以做哪些标准例外,但是 不过,我认为引入单独课程的大多数原因都是 寻址。
但是,不要忘记错误。我们改变了清单 回调一对回调函数列表,我们重写了 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类采用可选的异常参数来阻止它 使用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将是a 通过结果传递结果的虚拟(即失败实例) 不变。这样可以保留后续错误的错误条件 处理程序。为了便于添加错误处理程序而不进行处理 一个常规的resullt,我们添加addErrback(),如下所示:
def addErrback(self, errback): self.addCallback(lambda r: r, errback)
这里,该对的回调一半将通过(非失败)结果 通过不变到下一个回调。
如果您想要充分的动力,请阅读Twisted的简介 Deferreds;我最后注意到一个错误并替代了一个 仅通过返回非失败值来定义失败的结果 (包括无)。
在我继续讨论下一个想法之前,让我指出有这个想法 真正的延期课程中的更多细节。例如,您可以指定 要传递给回调和errback的其他参数。但在 你可以用lambdas做一个捏,所以我把它留下来,因为 进行管理的额外代码并没有阐明 基本想法。
理念4:链接延迟
这是一个五星级的想法!有时真的有必要 回调以等待额外的异步事件才能产生 期望的结果。例如,假设我们有两个基本的异步 operation,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()不带参数。