Twisted的延迟是否与JavaScript中的Promise相同?

问题描述 投票:15回答:1

我开始在需要异步编程的项目中使用Twisted,而且文档非常好。

所以我的问题是,在Javascript中延迟与扭曲是一样的承诺吗?如果没有,有什么区别?

javascript python twisted promise deferred
1个回答
7
投票

你的问题的答案是肯定的和否定的,这取决于你问的原因。

Yes:

Twisted Deferred和Javascript Promise都实现了一种机制,用于排队同步代码块,以便在与其他同步代码块分离的同时以给定顺序运行。

No:

所以Javascript的Promise实际上更类似于Python的Future,而通风的仙女解释这个就是讨论PromiseResolver被组合起来制作一个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()不带参数。

© www.soinside.com 2019 - 2024. All rights reserved.