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

How is reactive programming different than event-driven programming?

本文关键字:编程 何不同 反应式 事件驱动      更新时间:2023-09-26

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

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

反应式编程与Promises有何关联?我认为promise是事件驱动和回调地狱的替代方案。

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

事件驱动编程围绕着所谓的事件展开,这些事件是程序在发生某些事情时"激发"的抽象事物。代码中的其他地方"监听"事件,并在事件发生时响应他们需要做的事情。例如,事件可能是"用户按下此按钮"或"打印机已完成打印文档"。

反应式编程处理数据。最终,这是事件驱动编程的一个特殊情况。事件:数据已更改。事件处理程序:更改更多数据(如果适用)。当你想到电子表格时,这个概念通常会被澄清。如果设置cell1 = cell2 + cell3,则会在cell2cell3的数据更改事件上隐式设置两个事件处理程序,以更新cell1的数据。cell1的数据没有这样的事件处理程序,因为没有任何单元格依赖于它的值。


TL;DR;

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

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

  • 直觉编程:专注于改变程序的状态将实现您想要的。大多数计算机都是命令式的(与声明式编程相反),而更高级别的语言有时是声明式的。相反,声明式编程处理的是编写指定你希望它做什么的代码,而不是你希望代码如何做
  • 面向对象的编程:处理所谓的对象或具有相关方法的数据包。与函数式编程不同,因为方法能够访问与对象相关联的数据
  • 函数式编程:处理可重复使用的函数,或接受输入和输出的过程。这与OO编程不同,因为函数传统上不具备将数据与输入和输出以外的函数相关联的能力

事件驱动编程:构建程序,以便处理("处理")程序中发生的其他事情("事件")。换句话说,它像一样逻辑地构建代码

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)

反应式编程与Promises有何关联?

承诺是程序执行流程的抽象,可以概括为:

  • 阿斯克:每当你完成了你正在做的事情,你会给我回电话吗
  • 回答者:当然,我承诺

这里没有什么特别的,只是它是另一种思考代码执行顺序的方式。例如,当您对远程机器进行调用时,promise非常有用。有了承诺,你可以说"当你从这个远程电话回来时给我回电话!"。无论您使用哪一个库,都承诺在从远程机器返回内容时会给您回电话。通常,这很有用,因为它可以让你在不等待电话回复的情况下同时做其他事情。

最后一句话:有很多不同风格的代码,但它们在事件驱动和反应式编程模式中并没有发挥太大的作用。据我所知,你可以用大多数语言进行事件驱动和/或反应式编程。

反应式编程与Promises有何关联?我认为promise是事件驱动和回调地狱的替代方案。

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

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

Promise可以被认为是带有一个项目的EventStreams,也可以将EventStreams视为随时间推移的多个Promise。

承诺可以被连锁,这正接近于反应式编程:

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)
})

这两个代码片段都做着相同的事情,但在思维上有很大的区别:有了承诺,你会考虑以一种清晰的方式用异步步骤处理单个动作——思维是必要的,你会一步一步地做事。对于FRP,您会说"通过应用这两个转换步骤,从userIds流创建了一个用户名流"。当你有一个用户名流,而不在乎它们来自哪里,并说"只要有新的用户名,就向用户显示"。

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

差异主要与如何您"配置";(或宣布)事物惯例:当其他事情发生时,某事会发生什么。

在反应式编程中,您声明对更改的反应。你不必预先考虑对这种变化所需的反应,你可以在稍后添加-声明-这种反应。因此,它可以被认为是";"拉";或";手表;策略

因此,在反应式编程中,您连接到监视数据,您知道存在。数据在这里至关重要。

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

计算器应用程序示例:计算器显示绑定到所有按钮,并对任何更改(单击按钮)做出反应,并在显示上进行自己的更改。按钮没有意识到它们的点击可以被任何其他部分利用。

在事件驱动编程中,在命令式编写的代码中,在特定情况下触发事件。你需要在这里明确,因为事件需要首先被触发,以便稍后被接收-因为基本上你在";改变正在发生";代码的一部分。所以它是一个";"推";策略

因此,在事件驱动编程中,您在特定情况下推送一个事件可能会被代码的其他部分接收。形势很重要,数据无关紧要。

示例:有人访问了联系人页面->触发一个事件(最终任何侦听器都可能不会接收到该事件,这是许多模块和库的典型情况)。

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

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

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

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

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

反应式编程就是关于流的,它可以是事件流,也可以是其他任何东西。正是在发布/宣布这些流或订阅/观看这些流或流转换时,才导致了一些事件。因此,这两种编程范式是相关的。

对我来说,这就像把橙子比作苹果。让我们试着用一种简单的方式来定义什么是什么,从而区分事物:

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

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

反应式编程与Promises有何关联?我认为承诺是事件驱动和回调地狱的替代方案。

Promise是一种实现并发性和特定执行顺序的工具。它可以用于任何范式。

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