反应式编程与事件驱动编程有何不同?

How is reactive programming different than event-driven programming?

我正在 JavaScript 学习反应式编程和函数式反应式编程。我很困惑。

维基百科说有多种方式可以编写响应式代码,例如命令式、OORP 和函数式。我想知道事件驱动是否只是编写响应式代码的另一种方式?

响应式编程与 Promises 有什么关系?我认为 promises 是事件驱动和回调地狱的替代方案。

How is reactive programming different than event-driven programming?

事件驱动编程围绕所谓的事件展开,这些事件是在某些事情发生时编程 "fire" 的抽象事物。代码中的其他地方 "listen" 用于事件并响应事件发生时他们需要做什么。例如,事件可以是 "user pressed this button" 或 "the printer is done printing your document".

响应式编程处理 数据。归根结底,这是事件驱动编程的一个特例。事件:数据改变。事件处理程序:更改更多数据(如果适用)。当您想到电子表格时,通常会清楚这个概念。如果您设置 cell1 = cell2 + cell3,这会在 cell2cell3 的数据更改事件上隐式设置两个事件处理程序以更新 cell1 的数据。 cell1 的数据没有这样的事件处理程序,因为没有单元格依赖于它的值。


TL;DR;

Wikipedia says that there are various ways to write reactive code such as imperative, OORP and functional. I want to know if event-driven is just an another way to write reactive code?

事件驱动编程的思想与命令式、面向对象、函数式的思想是正交的。

  • Imperitive programming: focuses on changing the state of your program will achieve what you want. Most computers are imperative (as opposed to declarative programming),而高级语言有时是声明性的。相比之下,声明式编程涉及编写指定您希望它做什么而不是您希望代码如何做的代码。
  • Object Oriented programming:处理所谓的对象,或具有关联方法的数据包。不同于函数式编程,因为方法能够访问与对象关联的数据。
  • Functional programming:处理可重复使用的函数,或接受输入和输出的过程。这与 OO 编程不同,因为函数传统上无法将数据与输入和输出以外的函数相关联。

Event driven programming:构建程序以处理("handle")程序中发生的其他事情("event")。换句话说,它在逻辑上像这样构造你的代码

When Event1 happens
    do A and B

When Event2 happens
    do B and C

但是有很多方法可以编写这段代码,实际上有很多方法可以命令式编写代码,有很多方法可以函数式编写代码等等。不过,这里有一些例子。

命令式(使用事件循环):

while(true)
    // some other code that you need to do...

    if Event1 then
        do A
        do B
    if Event2 then
        do B
        do C

面向对象(带后台线程):

// event queue
events = new EventQueue()

handler = new EventHandler()
// creates background thread
Thread.DoInBackground(handler.listenForEvents(events))

// ... other code ...

// fire an event!
events.enqueue(new Event1())

// other file
class EventHandler
    Func listenForEvents(events)
        while(true)
            while events.count > 0
                newEvent = event.dequeue()
                this.handleEvent(newEvent)
            Thread.Sleep(Time.Seconds(1))

    Func handleEvent(event)
        if event is Event1
            this.A()
            this.B()
        if event is Event2
            this.B()
            this.C()

    Func A()
        // do stuff
        return

    Func B()
        // do stuff
        return

    Func C()
        // do stuff
        return

功能正常(事件有语言支持)

on Event(1) do Event1Handler()
on Event(2) do Event2Handler()

Func Event1Handler()
    do A()
    do B()

Func Event2Handler()
    do B()
    do C()

Func A()
    // do stuff
    return

Func B()
    // do stuff
    return

Func C()
    // do stuff
    return

// ... some other code ...

// fire! ... some languages support features like this, and others have
// libraries with APIs that look a lot like this.
fire Event(1)

How is reactive programming related to Promises?

Promises 是对程序执行流程的抽象,可以概括如下:

  • 提问者:当你做完你正在做的事情时,你会给我回电话吗?
  • 回答者:没问题,我保证

这里没什么特别的,除了它是考虑代码执行顺序的另一种方式。例如,当您调用远程机器时,promises 很有用。有了承诺,你可以说 "call me back when you return from this remote call!"。无论您使用哪个库, 都会承诺 在它从远程计算机返回信息时给您回电。通常,这很有用,因为它可以让您同时做其他事情,而无需等待 return.

的调用

妙语:有很多不同风格的代码,但它们在事件驱动和响应式编程的模式中并没有起到太大的作用。据我所知,您可以使用大多数语言进行事件驱动 and/or 反应式编程。

对我来说,这就像将橙子与苹果进行比较。让我们尝试以简单的方式定义什么是什么,以便区分事物:

响应式编程是一种编程范例,当人们想要实现类似于 KnockoutJS 等库中的数据绑定的功能时,就会应用它。还有一个例子是 Excel 公式:所有单元格都像内存中的变量。有些只保存一些数据,有些则根据该数据计算得出。如果前者改变,后者也会改变。请注意范例是关于较低级别的实现;当有人谈论响应式编程时,他们指的是数据、它的变化以及它发生变化时会发生什么。

另一方面,事件驱动编程是关于系统架构的。根据该范式,事件和事件处理程序是系统的基础,一切都建立在它们之上和周围。常见示例是 UI 和 Web 服务器多路复用。你觉得这一切有什么不同吗?该范式应用于整个系统或子系统的级别。

How is reactive programming related to Promises? I think promises is an alternative to event-driven and callback hell.

Promise 是一个实现并发和特定执行顺序的工具。它可以在任何范例中使用。

在实践中,这些范式服务于不同的目的和不同的层次。您可以使用一些响应式代码来进行事件驱动设计。您可以拥有使用反应式设计模式的分布式系统。但是,事件归根结底是更高层次的概念。 Reactive 是关于数据及其重新评估的,一种实现方法或其细节,而事件是从案例中自然产生并驱动您的设计的东西。

How is reactive programming related to Promises? I think the promise is an alternative to event-driven and callback hell.

在实践中,这两者是相关的,我喜欢将 Promises 称为功能响应式编程的门户药物。

+----------------------+--------+-------------+
|                      |  Sync  |    Async    |
+----------------------+--------+-------------+
| Single value or null | Option | Promise     |
| Multiple values      | List   | EventStream |
+----------------------+--------+-------------+

可以将 Promises 视为具有一项的 EventStreams,或者您可以将 EventStreams 视为随时间变化的多个 Promises。

Promises 可以被链接起来,这越来越接近反应式编程:

getUser() // return promise
   .then((userId) => {
       return fetch("/users/"+userId)
   })
   .then((user) => {
       alert("Fetched user: " + user.name)
   })

同bacon.js:

const userStream = userIdStream // EventStream of userIds
   .flatMapLatest((userId) => {
       return Bacon.fromPromise(fetch("/users/"+userId))
   })
const userNameStream = userStream.map((user) => user.name)
userNameStream.onValue((user) => {
   alert("Fetched user: " + user.name)
})

两个代码片段做同样的事情,但是在思维上有很大的不同:有了promises,你正在考虑以一种清晰的方式用异步步骤处理单个动作——思维是势在必行的,你在做事情的步骤循序渐进。有了FRP,你就成语了"a stream of usernames is created from the stream of userIds by applying these two transformation steps"。当你有一个用户名流,而不关心它们来自哪里,然后说 "whenever there is a new username, display it to the user".

FRP 编码风格将指导您将问题建模为值流(即随时间变化的值)以及这些值之间的关系。如果您已经了解 Promises,最初的学习曲线会更容易一些,但只有当您开始以不同的方式思考和建模问题时,才能获得主要好处——可以(如果不是很有用)使用 FRP 库进行命令式编程。

响应式编程都是关于流的,它可以是事件流,也可以是其他任何东西。正是在 emitting/announcing 这些流或 subscribing/watching 这些流或导致某些事件的流转换上。所以这两种编程范式都是相关的。

差异主要与如何“配置”(或声明)事物约定有关:当其他事情发生时,某事会发生什么。

在响应式编程中,您声明对更改的反应。您不必预先预见到该更改所需的这种反应,您可以在以后随时添加 - declare - 这种反应。因此,它可能被视为“拉动”或“观察”策略。

因此,在响应式编程中,你连接到/watch数据知道存在。数据在这里至关重要。

示例:用户点击了页面上的项目 -> 更新计数器用户点击了多少次。

示例计算器应用程序:计算器显示绑定到所有按钮,并且对任何变化(点击按钮)做出反应,在显示屏上显示自己的变化。按钮不知道它们的点击可以被任何其他部分使用。

在事件驱动编程中,你在命令式代码中触发某个情况的事件。您需要在这里预先明确,因为需要先触发事件以便稍后接收 - 因为基本上您 push 代码的“更改发生”部分中的事件。所以这是一个“推”的策略。

因此,在事件驱动编程中,你特定情况中推送事件] 可能 会被代码的其他部分接收。这里情况重要,数据不重要。

示例:有人访问了联系人页面 -> 触发了一个事件(最终可能根本没有被任何监听器接收到,这是许多模块的典型情况和图书馆)。

示例计算器应用程序:计算器显示只是一个监听器,按钮触发事件。按钮 需要 知道它们存在于特定的上下文中(但是 - 由于事件监听器模式 - 不必知道那个上下文到底是什么),因此它们是必需的触发事件。

所以在大多数情况下,它们只是不同的约定。 看看这个简单的例子。命令式方法示例:

event: perform some operation on a, e.g. a += value, and trigger the event
listener: counter++

以及反应式声明方法示例:

counter: whenever an operation on a occurs, react with this: counter++

在最后一个示例中不需要触发任何东西 - 您只需对可能发生的任何事情做出反应“挂钩”。

你可能会说,在反应式方法中,反应绑定到 a,而在命令式事件驱动方法中,你推送一个事件,稍后可以由侦听器接收 - 因为这种类型的方法与数据没有任何关系,您可以稍后将此 a += value 更改为其他任何内容,甚至完全删除 a

事件驱动方法本质上与数据无关。反应式编程基本上是关于数据的。

因此,如您所见,反应式编程是面向数据的(数据的变化会触发其他代码),而事件驱动的编程是面向过程的(数据是否发生变化以及什么数据发生变化并不重要,如果有的话) - 您只需触发一个事件,该事件将被代码的其他部分接收)。在后一种情况下,您需要 知道 这种“通知”代码的其他部分是必需的,然后您必须预见该事件应该被触发。在前一种情况下,你不必这样做,你可以随时这样做,或者根本不这样做——不需要触发事件——但这里的诀窍是必须有你可以连接的“东西”你的反应声明,一种观察者,可以让你对观察到的变化做出反应。