Future与promise

在计算机科学中，future、promise、delay和deferred，是在某些并发编程语言中，指称用于同步的一种构造。由于某些计算尚未结束，故而需要一个对象来代理这个未知的结果。这种构造起源于函数式编程和相关范型如逻辑编程，其目的是将值与其运算过程解耦，从而允许更灵活地进行计算，特别是通过将其并行化来进行。后来它在分布式计算中得到了应用，用来减少网络通信往返的延迟。再后来async/await语法机制使其变得更有用，籍此允许以直接风格编写异步程序，而不再采用续体传递风格。
术语.
在1976年Daniel P. Friedman和David Wise提出了术语“promise”，同年Peter Hibbard称之为“eventual”。1977年和在一篇论文中介绍了一个类似的概念“future”。
术语“future”、“promise”、“delay”和“deferred”通常用来称谓同样一种机制，在特定实现中可能只选用其中之一。在这种情况下，值与其运算过程是一起创建并且相互关联的：future若指称推迟设置的一个值，设置它的函数就可称谓为promise；promise若指称推迟设置的一个值，设置它的函数就可称谓为resolver；promise若指称一个异步函数，它的结果值所设置的就可称谓为future。设置一个推迟值也称为“决定/解决”（resolve）、“履行/实现”（fulfil）或“绑定”（bind）它。
推迟值及其设置者也存在区分称谓而同时使用的情况，二者的这种用法在不同实现中的差异将在专门章节中讨论。具体来说，future指称一个“只读”的变量的占位符视图，而promise指称一个可写的单赋值容器，使用其“成功”方法来设置这个future的值。尤其是在定义future之时，无须指定设置其值的promise；并且可能有不同的promise，设置同一个future的值；但是对于一个给定future，仅可以进行一次设置。
历史.
future及/或promise构造，首先实现于编程语言例如和Act 1之中。在语言中，使用非常类似于future的逻辑变量进行通信。这种语言开始于1982年的“Prolog with Freeze”和“IC Prolog”，并且在后来的语言中变成了真正的并发原语，比如Concurrent Prolog、、守卫霍恩子句（GHC）、、、Oz（Mozart）和。叫做“I-变量”的单赋值变量，非常类似于并发逻辑变量，它起源于数据流程编程语言的“I-结构”元件，并且包含在Reppy的Concurrent ML中。
在1988年Barbara Liskov和Liuba Shrira，发明了promise流水线技术来克服传输延迟；Liskov和Shrira在论文中使用了术语“promise”，但是他们采用了现在少见的名称“call-stream”来提及流水线机制。在大约1989年、Dean Tribble和Rob Jellinghaus，于Xanadu项目中也独立发明了此技术。
Liskov和Shrira的论文中描述的设计，以及Xanadu中的promise流水线的实现，都有一个限制，即promise值不是头等的：call或send的参数或返回值，不能直接是promise。在Liskov和Shrira论文中使用的编程语言Argus，直至大约1988年停止开发，似乎都未曾在任何公开发布中实现promise和call-stream。Xanadu实现的promise流水线，仅在1999年Udanax Gold的源代码发布时才公开发布，并且在任何已发布的文档中都没有解释过。
一些早期的演员语言，包括Act系列，支持并行消息传递和流水线式消息处理，但不支持promise流水线。和E语言的后续实现，支持完全头等的promise和resolver，还有promise流水线。
2000年之后，由于的请求-响应模型，在用户界面和Web开发中的应用，future和promise重新引起了人们的兴趣。现在一些主流语言对future和promise有了语言支持，最著名的是2004年发行的Java 5中的codice_1，以及2012年发行的.NET框架 4.5中的codice_2/codice_3结构，它在很大程度上受到可追溯到2007年的“F#异步编程模型”的启发。async/await随后被其他语言采用，特别是2014年的Dart 1.9、2014年发行的Hack（HHVM）、2015年的Python 3.5、ECMAScript 2017、2019年的Rust 1.39和C++20等。
实现列表.
编程语言支持.
下面列出支持future、promise和并发逻辑变量、数据流程变量或I-变量的语言，包括直接在语言中支持还有在标准库中支持：
还支持promise流水线的语言包括：
自行实现.
future可以用协程或生成器实现，从而具有相同的求值策略（例如协同多任务或延迟求值）。
future还可以很容易地用通道实现：future是一个单元素的通道，而promise是一个发送到通道，实现future的过程。这允许future在支持通道（如CSP和Go）的并发编程语言中实现。由此产生的future是显式的，因为它们必须通过从通道读取而不是仅仅通过求值来获取。
编程语言典型示例.
Python.
Python的codice_22模块，提供了可调用对象的高层接口。异步执行可以使用线程池执行器codice_23，通过多个线程来进行；或使用进程执行器codice_24，通过分立的多个进程来进行。codice_25类封装了可调用对象的异步执行，其实例由执行器抽象类codice_26的这两个子类的codice_27方法创建。
在下面的例子中，定义了codice_28，用来检索一个URL所指定的单一网页并报告其内容。使用codice_29语句指定采用线程池执行器，这确保了线程由它及时清理。用这个执行器的codice_27方法启动所有的装载操作任务，并使用字典推导式为其创建的每个future标记上对应的URL。采用模块函数codice_31，在指定的codice_32类的诸实例“已齐全”（completed）之时，建立在其上的迭代器。
<syntaxhighlight lang="python">
import concurrent.futures
import urllib.request
URLS = [
def load_url(url, timeout):
with concurrent.futures.ThreadPoolExecutor() as executor:
</syntaxhighlight>
在CPython中，由于全局解释器锁（GIL），保证一时只有一个线程可以执行Python字节码；一些扩展模块被设计为在进行计算密集任务时释放GIL，还有在进行I/O时总是释放GIL。线程池执行器的codice_33参数的缺省值是codice_34，这个缺省值为任务保留至少5个worker线程，为释放GIL的CPU踊跃任务利用最多32个CPU逻辑核心。想要更好利用多核心机器的计算资源，可以使用进程池执行器或多进程模块codice_35。对于同时运行多个任务，仍然适合采用线程池执行器或线程模块codice_36。
Python的异步I/O模块codice_16，是采用async/await语法编写并发代码的库。codice_16模块基于低层事件循环，提供的高层API包括了：并发运行Python协程并拥有在其执行上的完全控制，进行网络IO和IPC，控制子进程，通过队列分布和同步并发代码。codice_16模块的同步原语，在设计上类似于codice_36模块的同步原语，但与之相比有两个重要差异：codice_16模块的同步原语不是线程安全的，故而不能用于OS线程同步；这些同步原语的方法不接受实际参数。
codice_16模块提供的低层API中有codice_43对象，用来桥接低层基于回调的代码和高层采用codice_2/codice_3语法的代码，它在设计上模仿了codice_25对象。这两个模块的codice_32对象的codice_48方法标记这个codice_32对象为“已完毕”（done）并设置它的结果，而codice_50方法标记这个codice_32对象为已完毕并设置一个例外；codice_52方法在这个codice_32对象已完毕时返回codice_54；codice_55方法返回这个codice_32对象所设置的结果，或者引发其所设置的例外。
二者的关键差异包括了：codice_43是可期待（awaitable）对象，而codice_25对象不可以被期待（awaited）。codice_59不接受超时实际参数，如果此刻这个codice_32对象的结果仍不可获得，它引发一个codice_61例外；而codice_62，可接受（codice_63）实际参数，如果此刻结果仍未“完全”（completed），它等待指定时间后若仍未完全则引发codice_64例外，如果超时未指定或指定为codice_65，则对等待时间没有限制。
JavaScript.
在JavaScript中，codice_6对象表示一个运算的最终完成或失败，及其结果值或失败理由。codice_6对象是对一个值的代理（proxy），这个值在创建这个promise之时不必需已知。promise允许为异步行动的最终成功值或失败理由关联上处理器，这使得异步方法像同步方法那样返回值：并非立即返回最终值，异步方法返回一个promise来在将来的某一点上提供这个值。
在JavaScript生态系统中，codice_6对象在成为语言本身的一部份之前很久就有了多种实现</ref>。尽管各有不同的内部表示，至少几乎所有类似codice_6的对象都实现了“可接续”（thenable）接口。
同基于promise的代码合作的一种更简单的方式，是使用async/await关键字。在函数开始处增加codice_2，使其成为异步函数，函数总是返回一个promise。在异步函数中，可以在对返回一个promise的函数的调用之前，使用codice_3关键字；这使得代码在这一点上等待直到这个promise已落实下来，然后在这一点上promise的已履行的值被当作返回值，而已拒绝的理由被作为例外抛出。可以使用codice_72块进行例外处理，就如同这个代码是同步的一样。
只读视图.
在某些编程语言（如Oz、E和）中，可以获得推迟值的“只读视图”，允许在解决（resolve）出这个值之后通过它来读取，但不允许通过它来解决这个值：
对只读视图的支持符合最小特权原则，因为它使得设置值的能力仅限于需要设置该值的主体。在同样支持流水线的系统中，异步消息（具有结果）的发送方接收结果的只读promise，消息的目标接收resolver。
有关结构.
“future”是事件同步原语的特例，它只能完成一次。通常，事件可以重置为初始的空状态，因此可以根据需要多次完成。
“I-var”是具有下面定义的阻塞语义的future。它起源于Id语言中包含I-var的“I-structure”数据结构。可以使用不同值多次设置的有关同步构造称为“M-var”。M-var支持codice_80（采取）或codice_81（放置）当前值的原子性操作，这里采取这个值还将M-var设置回其初始的“空”状态。
“并发逻辑变量”与future类似，但是通过合一更新，与逻辑编程中的“逻辑变量”相同。因此，它可以多次绑定到可合一的值，但不能设置回到空或未解决状态。Oz的数据流变量充当并发逻辑变量，并且还具有上面提到的阻塞语义。
“并发约束变量”是并发逻辑变量的一般化，以支持约束逻辑编程：约束可以多次“缩小”，表示可能值的较小集合。通常，有一种方法可以指定每当约束进一步缩小时应该运行的；这是支持“约束传播”所必需的。
隐式与显式future.
对future的使用可以是“隐式”的，任何对future的使用都会自动获得它的值，它就像是普通的引用一样；也可以是“显式”的，用户必须调用函数来获取值，例如Java中的Future或CompletableFuture的codice_82方法。获得一个显式的future的值可以称为“刺激”（stinging）或“强迫”（forcing）。显式future可以作为库来实现，而隐式future则通常作为语言的一部分来实现。
最初的Baker和Hewitt论文描述了隐式future，它们在演员模型和纯面向对象编程语言（如Smalltalk）中自然得到支持。Friedman和Wise的论文只描述了显式的future，可能反映了在老旧硬件上有效实施隐式future的困难。难点在于老旧硬件不能处理原始数据类型（如整数）的future。例如，add指令不会处理codice_83 。在纯演员模型或面向对象语言中，这个问题可以通过向codice_84发送消息codice_85来解决，它要求future自己加codice_86并返回结果。请注意，无论codice_87何时完成计算，消息传递方法都可以工作，而且不需要任何“刺激”或“强迫”。
阻塞与非阻塞语义.
如果future的值是异步访问的，例如通过向它发送消息，或者通过使用类似于E语言中的codice_88的构造显式地等待它，那么在消息可以被接收或等待完成之前，推迟直到future得到解决（resolve）是没有任何困难的。这是在纯异步系统（如纯演员语言）中唯一需要考虑的情况。
然而，在某些系统中，还可能尝试“立即”或“同步”访问future的值。这样的话就需要做出一个设计选择：
作为第一种可能性的示例，在C++11中 ，需要future值的线程可以通过调用codice_89或codice_90成员函数来阻塞，直到它可获得为止。还可以使用codice_91或codice_92成员函数指定等待超时，以避免无限期阻塞。如果future对codice_77的调用，那么阻塞等待（没有超时）可能导致函数的同步调用以计算等待线程上的结果。
promise流水线.
在分布式系统中使用推迟值可以显著地减少传输延迟。例如，指称推迟值的promise，成就了“promise流水线”，就像在E语言和语言中实现的那样，它在Argus语言中称为“call-stream”。
考虑一个涉及常规远程过程调用的表达式，例如：
可以展开为
每个语句需要发送一条消息，并在下一个语句可以继续之前收到一个答复。例如，假设codice_94、codice_95、codice_96和codice_97都位于同一台远程机器上。在这种情况下，在开始执行第三条语句之前，必须对该机器进行两次完整的网络往返。然后，第三条语句将引起另一个到同一个远程机器的往返。
上面的表达式可以使用E语言的语法写为：
其中codice_98表示将消息codice_99异步发送给codice_94。它可以展开为：
所有三个变量都会立即为其结果分配promise，执行过程将继续进行到后面的语句。之后尝试解决codice_101的值可能会导致传输延迟；但是，流水线操作可以减少所需的往返次数。如果与前面的示例一样，codice_94、codice_95、codice_96和codice_97都位于相同的远程机器上，则流水线实现可以用一次往返来计算codice_101，不必用三次。由于所有三条消息都指向同一远程计算机上的对象，因此只需要发送一个请求，只需要接收一个包含结果的响应。另请注意，即使codice_96和codice_97位于不同机器上，或者位于与codice_94或codice_95不同的机器上，发送codice_111也不会阻塞。
promise流水线应与并行异步消息传递区分开来。在支持并行消息传递但不支持流水线操作的系统中，上面示例中的消息发送codice_98和codice_113可以并行进行，但发送codice_111将不得不等到codice_96和codice_97都被接收，即使codice_94、codice_95、codice_96和codice_97在同一个远程机器上。在涉及许多消息的更复杂情况下，流水线的相对传输延迟优势变得更大。
promise流水线操作也不应与演员系统中的流水线式消息处理相混淆，在这种系统中，演员可以在完成当前消息的处理之前，指定并开始执行下一个消息的行为。
有特定线程的future.
某些语言比如，定义的future可以关联着计算这个future值的特定线程。这种计算可以通过codice_121，在创建future时及早地开始；或者通过codice_122，在首次需要其值时懒惰地开始。在延迟计算的意义上，懒惰的future类似于thunk 。
Alice ML还支持可由任何线程解决的future，并称谓它们为“promised future”。这里的promise是给future的显式把柄（handle），它通过多态库函数codice_123来创建，所有promise都关联的一个future，创建一个新promise也就创建了一个新鲜的future；这个future通过codice_124来提取，并且通过显式的应用codice_125于对应的promise来消除，即在全局中将这个future替代为一个值。Alice ML不支持promise流水线，转而对于future，包括关联着promise的future，流水线是自然而然地发生的。
在没有特定线程的future（如Alice ML所提供的）中，通过在创建这个future的同时创建一个计算这个值的线程，可以直接实现及早求值的有特定线程的future。在这种情况下，最好将只读视图返回给客户，以便仅让新创建的线程能够解决这个future。
要在没有特定线程的future中，实现隐式惰性的有特定线程的future，需要一种机制来确定何时首次需要future的值（例如，Oz中的codice_126构造 ）。
如果所有值都是对象，那么有实现透明转发对象的能力就足够了，因为发送给转发器的首条消息表明需要future的值。
假定系统支持消息传递，在有特定线程的future中，通过让解决线程向future自己的线程发送消息，可以实现没有特定线程的future。然而这可能被视为不必要的复杂性。在基于线程的编程语言中，最具表现力的方法似乎是提供一种混合：没有特定线程的future、只读视图、以及要么有codice_126构造要么支持透明转发。
传future调用.
就求值策略而言，“传future调用”是非确定性的：future的值将在创建future和使用其值之间的某个时间进行求值，但确切的时间不确定的，一次运行和另一次运行的求值时间会不一样。计算可以在创建future时开始（及早求值），或者仅在实际需要值时开始（懒惰求值），并且可以在中途暂停，或在一次运行中执行。一旦future被赋值，它就不会在访问future的时候重新计算；这就像传需求调用时使用的记忆化。
“懒惰”future是确定性的具有惰性求值语义的future：future值的计算在首次需要时开始，与传需要调用一样。懒惰future使用在求值策略默认不是懒惰求值的语言中。例如，在C++11中，可以通过将codice_128启动策略传递给codice_77以及计算值的函数来创建这种惰性future。
演员模型中的future语义.
在演员模型中，形式为codice_130的表达式，以它对具有环境E和客户C的codice_131消息的响应方式来定义：future表达式通过向客户C发送新创建的演员F（计算codice_132的响应的代理）作为返回值来响应codice_131消息，与之并发的向codice_132发送具有环境E和客户F的codice_131消息。F的默认行为如下：
但是，一些future可以通过特殊方式处理请求以提供更大的并行性。例如，表达式codice_138可以创建一个新的future，其行为类似于数字codice_139 。这个技巧并不总是有效。例如，以下条件表达式：
会挂起，直到codice_141这个future已回应询问codice_142是否大于其自身的请求。