扭曲被推迟和JavaScript中的承诺一样吗?

时间:2014-04-29 21:08:57

标签: javascript python twisted promise deferred

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

所以我的问题是,在Twisted中延迟是否与Javascript中的Promise相同?如果没有,有什么区别?

1 个答案:

答案 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()不带参数。