异步解耦_如何使用异步生成器解耦业务逻辑

异步解耦

Async generators are new in JavaScript. They are a remarkable extension. They provide a simple but very powerful tool for splitting programs into smaller parts, making sources easier to write, read, maintain and test.

异步生成器是JavaScript中的新增功能。 它们是一个了不起的扩展。 它们提供了一个简单但非常强大的工具,用于将程序拆分为较小的部分,从而使源代码更易于编写,阅读,维护和测试。

The article shows this using an example. It implements a typical front-end component, namely drag and drop operations. The same technique is not limited to the front end It is hard to find where it cannot be applied. I use the same in two big compiler projects, and I’m very excited how much it simplifies there.

本文通过一个示例展示了这一点。 它实现了一个典型的前端组件,即拖放操作。 相同的技术不仅限于前端。很难找到不能应用的地方。 我在两个大型编译器项目中使用了相同的代码,我很兴奋在那里简化了很多代码。

The final result is a toy, but most of the code there is usable in a real-world application. The only goal of the article is to show how to split the program into smaller independent parts using async generator functions. It is not an article about how to implement drag and drop.

最终的结果是一个玩具,但是其中的大多数代码都可以在实际的应用程序中使用。 本文的唯一目的是展示如何使用异步生成器功能将程序拆分为较小的独立部分。 这不是一篇有关如何实现拖放的文章。

There is transpiled demo of what we’ll get at the end of the day.

最终,我们将获得转译的演示

You can drag boxes from a palette in the top and drop into any of the gray areas. Each drop area has its specific actions. A few items can be selected. Yellow ones have inertial movement.

您可以将框从顶部的调色板中拖放到任何灰色区域。 每个放置区域都有其特定的动作。 可以选择一些项目。 黄色的具有惯性运动。

All the features are independent there. They are split into stages for each feature. They are simple to enable, disable, develop, test and debug separately. A few developers or teams could productively work on it in parallel.

所有功能都在那里独立。 对于每个功能,它们分为多个阶段。 它们易于分别启用,禁用,开发,测试和调试。 一些开发人员或团队可以并行高效地工作。

I assume some the very basic knowledge of async generators (or at least of async functions and generators separately) and some fundamentals of HTML DOM (at least knowing what it is). There are no dependencies on third-party JavaScript libraries or frameworks, but the same technique may be used with any of them.

我假设了一些异步生成器的基本知识(或至少分别是异步函数和生成器)以及HTML DOM的一些基础知识(至少知道它是什么)。 第三方JavaScript库或框架没有依赖关系,但是它们中的任何一种都可以使用相同的技术。

For the demo, let’s pretend we don’t know full requirements set and add new a feature only after we finish something and it works. Playing with already working software at intermediate stages typically boosts creativity. It is one of the main components of the agile software development core - better to write something not perfectly designed but working first. Then we can always improve it using refactoring after. Async generators will help.

对于该演示,我们假设我们不了解完整的要求集,仅在完成某些工作并且可以正常工作后才添加新功能。 在中间阶段使用已经运行的软件通常可以提高创造力。 它是敏捷软件开发核心的主要组成部分之一-最好编写一些并非经过精心设计但可以首先工作的东西。 然后,我们总是可以使用重构来改善它。 异步生成器将有所帮助。

At the beginning of any project, I don’t want to spend time on choosing the right framework, library or even an architecture. I don’t want to overdesign. With the help of async iterators, I can delay the hard decisions to a point with enough knowledge to make a choice. The earlier I take some option, the more chances there are for mistakes. Maybe I won’t need anything at all.

在任何项目开始时,我都不想花时间选择正确的框架,库甚至架构。 我不想过度设计。 在异步迭代器的帮助下,我可以将艰难的决策延迟到足够多的知识来做出选择。 我越早采取一些选择,出错的机会就越大。 也许我根本不需要任何东西。

I’ll describe a couple of steps only. The other steps are small and can be read directly from code effortlessly. They are just a matter of working with DOM.

我将仅介绍几个步骤。 其他步骤很小,可以轻松地从代码中直接读取。 它们只是与DOM合作的问题。

异步生成器 (Async generators)

All the samples share nano-framework sources. It is developed once, at the beginning and copy-pasted without any change. In the real project, these are separate modules, imported into other modules if needed. The framework does just one thing. It converts DOM events into async iterator elements.

所有样品共享纳米框架的来源。 它一开始就开发一次,无需任何更改即可复制粘贴。 在实际项目中,这些是单独的模块,如果需要,可以将其导入其他模块。 该框架只做一件事。 它将DOM事件转换为异步迭代器元素。

Async iterators have the same next method like ECMAScript plain iterators, but they return a Promise resolving to Objects with value, done fields.

异步迭代器具有与ECMAScript纯迭代器相同的next方法,但是它们将Promise解析为具有valuedone字段的Object。

Async generators combine the functionality of async functions and generators. In the bodies of such functions, we can use await together with yield expressions, and they do exactly what these expressions do in async functions and generators, respectively. Namely they suspend execution control until the Promise in the  await argument is resolved, and for yield outputs value and suspends until caller requests next value.

异步生成器结合了异步函数和生成器的功能。 在此类函数的主体中,我们可以将awaityield表达式一起使用,它们分别在异步函数和生成器中分别执行这些表达式。 也就是说,它们将暂停执行控制,直到解析了await参数中的Promise为止;对于yield输出值,将暂停执行直到调用者请求下一个值为止。

Here’s the nano-framework implementation, with the first version of business logic (monolithic for now):

这是纳米框架的实现,带有业务逻辑的第一个版本(目前为单片):

It is a working sample, press Result there to see it in action. There are four elements you can drag within the page. The main components are send, produce and consume. The application subscribes to DOM events and redirects them into the framework using the send function. The function converts the arguments into elements of the async iterator returned by produce call. The iterator never ends and called at a module’s top level.

这是一个有效的示例,请按“ 结果”以查看它的实际效果。 您可以在页面中拖动四个元素。 主要组成部分是sendproduceconsume 。 该应用程序订阅DOM事件,并使用send函数将其重定向到框架中。 该函数的参数转换成迭代器返回由异步的元素produce的呼叫。 迭代器永远不会结束,并在模块的顶层调用。

There is a for(;;) loop in produce. I know, it looks suspicious, you may even have it denied in your team code review checklist or event by some lint rule. For code readability we of course want the exit condition for loops to be obvious. But this loop should never exit, as it is supposed to be infinite. It doesn’t consume CPU cycles since most of the time it will sleep in await and yield expressions there.

produce一个for(;;)循环。 我知道,它看起来很可疑,您甚至可能由于某些棉绒规则而在团队代码审查清单或事件中拒绝了它。 为了提高代码的可读性,我们当然希望循环的退出条件是显而易见的。 但是这个循环永远都不会退出,因为它应该是无限的。 它不占用CPU周期,因为大多数时间它会在await状态下Hibernate并在那里yield表达式。

There is also the consume function. It reads any async iterator in its argument, doing nothing with the elements, never returning. We need it to keep our framework running.

还有consume功能。 它读取其参数中的任何异步迭代器,不处理元素,永不返回。 我们需要它来保持我们的框架运行。

async function consume(input) {  
    for await(const i of input) {}
}

It is an async function (not generator), but it uses the new for-await-of statement, an extension of the for-of statement. It reads async iterators, rather than the original ECMAScript iterator, and awaits each element. Its simplified implementation could transpile the original consume code into something like this:

它是一个异步函数(不是生成器),但是它使用了新的for-await-of语句,它是for-of语句的扩展。 它读取异步迭代器,而不是原始的ECMAScript迭代器,并等待每个元素。 它的简化实现可以将原始的consume代码转换成如下形式:

async function consume(input) {
    const iter = input[Symbol.asyncIterator]()    
    for(let i;(i = await iter.next()).done;) {}
}

The main function is an entry point of the application’s business logic. The function is called between produce and consume in the module’s top level.

main功能是应用程序业务逻辑的入口点。 该函数在模块的顶层之间在produceconsume之间调用。

consume(main(produce()))

There is also a small share function. We need it to use the same iterator in a few for-await-of statements.

还有一个小的share功能。 我们需要它在一些for-await-of语句中使用相同的迭代器。

The first monolithic version of business logic is fully defined in main. It is a first dirty draft version, but the power of async generators is already visible. The application state (where we started dragging — x, y variables) is just simple local variables, encapsulated inside the function.

main完全定义了业务逻辑的第一个整体版本。 这是第一个肮脏的草稿版本,但是异步生成器的功能已经可见。 应用程序状态(我们开始拖动的位置xy变量)只是封装在函数内的简单局部变量。

Besides data state, there is also execution control state. It is a kind of implicit local variable storing position where the generator is suspended (either on await or yield). Though the real magic begins when we start splitting the main.

除了数据状态外,还有执行控制状态。 这是一种隐式局部变量存储位置,在该位置上生成器被挂起(处于awaityield )。 虽然真正的魔术在我们开始分解main时开始。

分裂 (Splitting)

The most often used function combination is their composition: say for function f and g a composition of two functions is a => f(g(a)).

最常用的函数组合是它们的组合:对于函数fg ,两个函数的组合为a => f(g(a))

If we compose plain functions, the next one starts doing its job only after the former one exists. If they are running generators, their execution is interleaved.

如果我们编写普通函数,则下一个函数仅在前一个函数存在后才开始执行其工作。 如果它们正在运行生成器,则其执行是交错的。

A few composed generator functions make a parallel pipeline. Like in any manufacturing process, say cars, splitting jobs into a few steps using an assembly line significantly increases productivity. Similarly, in the pipeline based on async generators, some function may send messages to the next using values its result iterator yields. The following function may do something application specific depending on a content of the message or pass it to the next stage.

几个组合的生成器函数构成了并行管道。 就像在汽车的任何制造过程中一样,使用装配线将工作分成几个步骤,可以显着提高生产率。 类似地,在基于异步生成器的管道中,某些函数可能会使用其结果迭代器产生的值将消息发送到下一个。 以下功能可以根据消息的内容执行特定于应用程序的操作,或者将其传递到下一个阶段。

These stage functions are the component of business logic. More formally they are any JavaScript function, taking an async iterator as its parameter and returning another async iterator as a result. In most cases, this will be an async generator function, but not necessarily.

这些阶段功能是业务逻辑的组成部分。 更正式地讲,它们是任何JavaScript函数,将异步迭代器作为其参数,并返回另一个异步迭代器。 在大多数情况下,这将是异步生成器函数,但不是必须的。

There are many names commonly in use for such functions now. For example Middleware, Epic, etc., I like the name Transducer more and will use it in the article.

现在,此类功能通常使用许多名称。 例如Middleware,Epic等,我更喜欢Transducer这个名字,并将在本文中使用它。

Transducers are free to do whatever they want with the input stream. Here are examples of what this can be:

换能器可以随意处理输入流。 这是可能的示例:

  • pass through the message to next step (with yield i)

    将消息传递到下一步(带有yield i )

  • change something in it and pass next (yield {…i,one:1})

    更改其中的内容并继续传递( yield {…i,one:1} )

  • generate a new message (yield {type:”two”,two:2})

    生成一条新消息( yield {type:”two”,two:2})

  • don’t yield anything at all thus filtering the message out

    根本不产生任何东西,从而将消息过滤掉
  • buffer the messages in some array and output on some condition (yield* buf), e.g., delaying drag start to avoid false response

    将消息缓冲在某个数组中,并在某些条件下( yield* buf )输出,例如,延迟拖动开始以避免错误的响应

  • do some async operations (await query())

    做一些异步操作( await query() )

Transducers mostly listen for incoming messages on for-await-of loops. There may be a few such loops in a single transducer body. This utilizes execution control state to implement some business logic requirements.

换能器大多在for-await-of循环中侦听传入的消息。 在单个换能器主体中可能会有一些这样的回路。 这利用执行控制状态来实现一些业务逻辑需求。

Let’s see how it works. We'll split the monolithic main function from the above sample into two stages. One converts DOM events into drag and drop messages — makeDragMessages (types "dragstart", "dragging", "drop") and the other updates DOM positions — setPositions. The main function is just a composition of the two.

让我们看看它是如何工作的。 我们将从上述示例中将单片main函数分为两个阶段。 一种将DOM事件转换为拖放消息makeDragMessages (类型为"dragstart""dragging""drop" ),另一种将DOM位置更新为setPositionsmain功能只是两者的组合。

I split the program here because I want to insert some new message handlers between them. It's the same as when writing new software, I wouldn’t focus too much on how to split the code correctly before I understand why I needed this. It should satisfy some reasonable size constraint. They also must be separated on logically different features.

我在这里拆分程序是因为我想在它们之间插入一些新的消息处理程序。 与编写新软件时相同,在我理解为什么需要这样做之前,我不会过多地关注如何正确地拆分代码。 它应该满足一些合理的大小限制。 它们还必须在逻辑上不同的功能上分开。

The main function there is actually a transducer too (takes an async iterable and returns an async iterable). It is an example of a transducer which is not an async generator itself. Some larger application may inject main from this module into other pipelines.

那里的main功能实际上也有一个转换器(接受异步可迭代并返回异步可迭代)。 这是换能器的示例,它本身不是异步发生器。 某些较大的应用程序可能会将这个模块中的main注入其他管道。

This is the final version of the nano-framework. Nothing is to be changed there regardless of what new features we add. The new features are function specified somewhere in the chain in main.

这是纳米框架的最终版本。 无论我们添加了什么新功能,都不​​会更改。 新功能是在main的链中某处指定的功能。

主要特点 (First features)

Now back to making something new. Just dragging something on a page isn't enough. We have special message names for dragging ("dragstart", "dragging", "drop"). Next, transducers can use them instead of mouse/touch events. For example, we can add a keyboard support, changing nothing for this.

现在回到制作新事物。 仅在页面上拖动内容是不够的。 我们有用于拖动的特殊消息名称( "dragstart""dragging""drop" )。 接下来,换能器可以使用它们代替鼠标/触摸事件。 例如,我们可以添加键盘支持,对此不做任何更改。

Let’s make a way to create new draggable items, some area where we can drag them from, and something to remove them. We’ll also flavor it with animation on dropping an item in the trash area or outside of any area.

让我们找到一种创建新的可拖动项目的方法,从中可以拖动它们的区域以及将其删除的区域。 我们还将在将某个项目放到垃圾箱区域或任何区域之外时用动画对其进行调味。

First, everything starts with the palette transducer. It detects drag start on one of its elements, clones it into a new element, and replaces all the original dragging events after with the clone. It is absolutely transparent for all the next transducers. They know nothing about the palette. For them, this is like another drag operation of the existing element.

首先,一切都从palette传感器开始。 它检测到其元素之一上的拖动开始,将其克隆到一个新元素中,然后使用克隆替换所有原始拖动事件。 对于所有后续换能器而言,它绝对透明。 他们对调色板一无所知。 对于他们来说,这就像现有元素的另一个拖动操作。

Next the  assignOver transducer does nothing visible for the end-user, but it helps the next transducers. It detects HTML elements a user drags an item over and adds it to all messages using the over property. The information is used in the  trash and  validateOver transducers to decide if we need to remove the element or cancel the drag.

接下来,对最终用户而言, assignOver转换器不执行任何操作,但对后续的转换器assignOver帮助。 它检测用户拖动项目HTML元素,并使用over属性将其添加到所有消息中。 该信息用于trashvalidateOver传感器中,以确定是否需要删除元素或取消拖动。

The transducers don’t do that themselves but rather send "remove" or "dragcancel" messages to be handled by something next. The cancel message is converted to "remove" by removeCancelled. And "remove"messages are finally handled in applyRemove by removing them from the DOM.

换能器本身并不执行此操作,而是发送"remove""dragcancel"消息以供下一步处理。 取消消息由removeCancelled转换为"remove" 。 最后,通过从DOM中删除"remove"消息,它们在applyRemove中得到处理。

By introducing another message type, we can inject new features implementations in the middle without replacing anything in the original code. In this example it is animation. On "dragcancel" the item moves back to its original position, and on "remove" its size is reduced to zero. Disabling/enabling animation is just a matter of removing/inserting transducers at some specific position.

通过引入另一种消息类型,我们可以在中间插入新的功能实现,而无需替换原始代码中的任何内容。 在此示例中,它是动画。 在"dragcancel"该项目将移回其原始位置,在"remove"其大小将减小为零。 禁用/启用动画只是在某些特定位置移除/插入换能器的问题。

The animation will continue to work if something else generates "dragcancel" or "remove". We may stop thinking about where to apply it. Our business logic becomes more high level.

如果其他东西产生了"dragcancel""remove" ,动画将继续工作。 我们可能会停止考虑将其应用于何处。 我们的业务逻辑变得更高层次。

The animation implementation also utilizes async generators but not in the form of transducers. This is a function returning values from zero to one in animation frames with specified delay, which defaults to 200ms. And the caller function uses it in whatever way it likes. Check for the demo animRemove function in the fiddle above.

动画实现还利用了异步生成器,但没有采用转换器的形式。 此函数以指定的延迟(默认值为200ms)在动画帧中从零返回一的值。 调用者函数会以任何喜欢的方式使用它。 检查上方小提琴中的demo animRemove函数。

Many other animation options are simple to add. The values may be not linear but output with some spline function. Or it may be based not on delay but on velocity. This is not significant for functions invoking anim.

许多其他动画选项很容易添加。 该值可能不是线性的,但会输出一些样条函数。 或者它可能不是基于延迟,而是基于速度。 这对于调用anim功能而言并不重要。

多选 (Multi-select)

Now let’s add incrementally another feature. We start from scratch, from the nano-framework. We will merge all the steps in the end effortlessly. This way the code from the previous step will not interfere with the new development. It is much easier to debug and write tests for it. There are no unwanted dependencies as well.

现在,让我们逐步添加另一个功能。 我们从零开始,从纳米框架开始。 最后,我们将毫不费力地合并所有步骤。 这样,上一步中的代码将不会干扰新的开发。 调试和编写测试要容易得多。 也没有不必要的依赖关系。

The next feature is a multi-select. I highlight it here because it requires another higher order function combination. But at first, it looks straightforward to implement. The idea is to simulate drag messages for all selected elements when a user drags one of them.

下一个功能是多选。 我在这里强调它,因为它需要另一个更高阶的函数组合。 但是起初,它看起来很容易实现。 这个想法是当用户拖动一个选定元素时,模拟所有选定元素的拖动消息。

Implementation is very simple but it breaks the next steps in the pipeline. Some transducers (like setPosition) expect an exact message sequence. For a single item, there should be "dragstart" followed by a few "dragging" and a "drop" in the end. This is no longer true.

实现非常简单,但是却中断了后续步骤。 一些换能器(例如setPosition )期望确切的消息序列。 对于单个项目,应先进行"dragstart" "dragging" ,然后再进行一些"dragging""drop" 。 这不再是事实。

A user drags a few elements at the same time. So there’ll be messages now for several elements simultaneously. There is only one start coordinate in setPosition x and y local variables. And its control flow is defined only for one element. After "dragstart" it is in the nested loop. It doesn’t recognize any next "dragstart" until exiting that loop on "drop".

用户同时拖动几个元素。 因此,现在将同时有几个元素的消息。 setPosition xy局部变量中只有一个开始坐标。 并且其控制流仅针对一个元素定义。 在"dragstart"它处于嵌套循环中。 在退出"drop"上的循环之前,它不会识别任何下一个"dragstart" "drop"

The problem can be solved by resorting to storing state, including a control state, in some map for each element currently dragging. This would, of course, break all async generator advantages. I have also promised there are no changes to the nano-framework. So it is not the solution.

该问题可以通过在某些映射中针对当前拖动的每个元素求助于存储状态(包括控制状态)来解决。 当然,这将破坏所有异步生成器的优势。 我还承诺纳米框架不会有任何变化。 因此,这不是解决方案。

What we need here is to run transducers expecting to work with a single element in a kind of a separate thread. There is a byElement function for this. It multiplexes input into a few instances of a transducer passed as its argument. The instances are created by calling the transducer in the argument supplying its filtered source iterator. Each source for each instance outputs only messages with the same element field. The outputs of all the instances are merged back into one stream. All we need to do is to wrap transducers with byElement.

我们这里需要的是运行换能器,以期望在单个线程中与单个元素一起工作。 byElement有一个byElement函数。 它将输入多路复用到作为其参数传递的换能器的几个实例中。 通过在提供其过滤后的源迭代器的参数中调用换能器来创建实例。 每个实例的每个源仅输出具有相同element字段的消息。 所有实例的输出都合并回一个流。 我们需要做的就是用byElement包装换能器。

First, it converts DOM events into application-specific messages in makeSelectMessages. The second step adds a selection indicator and highlights selected items after selections ending in selectMark. Nothing is new in the first two. The third transducer checks if a user drags a highlighted item. If so, it gets all other highlighted items and generates drag and drop messages for each of them in propagateSelection. Next setPosition runs in a thread per each element.

首先,它将DOM事件转换为makeSelectMessages特定于应用程序的消息。 第二步添加一个选择指示器,并在以selectMark结尾的选择之后突出显示选定的项目。 前两个没有什么新内容。 第三换能器检查用户是否拖动突出显示的项目。 如果是这样,它将获取所有其他突出显示的项目,并在propagateSelection为每个项目生成拖放消息。 下一个setPosition在每个元素的线程中运行。

最后结果 (Final result)

After the multi-selection feature is implemented, it is done once and for all. The other features just automatically work with it. All we need to change is to add it to main and correctly wrap other transducers with byElement if needed. This may be done either in main or in a module where the transducers are imported from.

实施多选功能后,将一劳永逸。 其他功能只是自动使用。 我们需要更改的只是将其添加到main并在需要时用byElement正确包装其他换能器。 这可以在导入传感器的main模块或模块中完成。

Here is the fiddle with the final demo with all the features merged:

这是合并了所有功能的最终演示的小提琴:

All the transducers are in fact a very lightweight thread. Unlike real threads, they are deterministic but they use non-deterministic DOM events as a source. So they must be considered non-deterministic as well.

实际上所有换能器都是很轻的螺纹。 与真实线程不同,它们是确定性的,但它们使用非确定性DOM事件作为源。 因此,它们也必须被视为不确定的。

This makes all the typical problems of multi-threaded environments possible, unfortunately. These are racings, deadlocks, serializations, etc. Fortunately, they are simple to avoid. Just don’t use mutable shared data.

不幸的是,这使所有多线程环境的典型问题成为可能。 这些是竞赛,僵局,序列化等。幸运的是,它们很容易避免。 只是不要使用可变的共享数据。

I violate this constraint in the demo by querying and updating the DOM tree. It doesn’t lead to problems here, but in the real application, it is something to care about. For fixing this, some initial stages may read everything needed from a DOM and pack it into messages. The final step may perform some DOM updates based on messages received. This may be some virtual DOM render, for example.

我在查询中通过查询和更新DOM树来违反此约束。 它不会在这里导致问题,但是在实际应用中,这是值得关注的事情。 为了解决此问题,某些初始阶段可能会从DOM中读取所需的所有内容并将其打包为消息。 最后一步可以根据收到的消息执行某些DOM更新。 例如,这可能是一些虚拟DOM渲染。

Communicating with the messages only allows isolating the thread even more. This may be a Web Worker, or even a remote server.

与消息通信仅允许进一步隔离线程。 这可能是Web Worker,甚至是远程服务器。

But again, I wouldn’t worry before it becomes a problem. Thanks to async iterators, the program is a set of small, isolated and self-contained components. It is straightforward to change anything when (if) there is any problem.

但是,我再也不担心它会成为问题。 多亏了异步迭代器,该程序才是一组小型,隔离且自包含的组件。 当有任何问题时,很容易更改任何内容。

The technique is compatible with other design techniques. It will work for OOP or FP. Any classic design pattern applies. When the main function grows big, we can add some dependency injection to manage the pipeline, for example.

该技术与其他设计技术兼容。 它将适用于OOP或FP。 任何经典的设计模式都适用。 例如,当main函数变大时,我们可以添加一些依赖注入来管理管道。

The technique reduces worry about the application’s architectures. Only write a specific transducer for each feature you need to implement. Abstract common parts into stand-alone transducers. Split it into a few if something else is to be done in the middle. Generalize some parts into abstract reusable combinators only when(if) you have enough knowledge for this.

该技术减少了对应用程序体系结构的担心。 只需为您需要实现的每个功能编写一个特定的转换器。 将通用部分提取到独立的换能器中。 如果要在中间进行其他操作,请将其拆分为几个。 仅在您有足够知识的情况下,才将某些部分归纳为抽象的可重用组合器。

与其他图书馆的关系 (Relation to other libraries)

If you are familiar with node-streams or functional reactive libraries such as RxJS, you can probably already spot many similarities. The only difference is what interface is used for streams.

如果您熟悉节点流或功能性React式库(例如RxJS) ,则可能已经发现了许多相似之处。 唯一的区别是什么接口用于流。

Transducers don’t need to be async generators. It is just a function taking a stream and returning another stream regardless of what interface the stream has. The same technique to split business logic may be applied to any other stream interfaces. Async generators just provide excellent syntax extension for them.

换能器不需要是异步发电机。 它只是一个获取流并返回另一个流的函数,而不管该流具有什么接口。 分割业务逻辑的相同技术可以应用于任何其他流接口。 异步生成器只是为其提供了出色的语法扩展。

If you are familiar with Redux you may notice message handlers are very similar to middlewares or reducers composition. Async iterators can be converted into Redux middleware as well. Something like this, for example, is done in redux-observable library but for a different stream interface.

如果您熟悉Redux,您可能会注意到消息处理程序与中间件或reducers的组成非常相似。 异步迭代器也可以转换为Redux中间件。 例如,类似的事情是在redux-observable库中完成的,但要使用不同的流接口。

Though, this violates the Redux principles. There is no longer a single storage. Each async generator has its own encapsulated state. Even if it doesn’t use local variables the state is still there. It is the current control state and position in the code where the generator was suspended. The state is also not serializable.

不过,这违反了Redux原则 。 不再有单个存储。 每个异步生成器都有其自己的封装状态。 即使它不使用局部变量,状态仍然存在。 它是代码中暂停生成器的当前控制状态和位置。 该状态也不可序列化。

The framework fits nicely with the Redux underlying patterns though, like Event Sourcing. We can have a specific kind of message propagating some global state diffs. And transducers can react accordingly, probably updating their local variables if needed.

该框架非常适合Redux底层模式,例如Event Sourcing 。 我们可以通过某种特定的消息传播某些全局状态差异。 换能器可以做出相应的React,如果需要,可以更新其局部变量。

The name, transducer, is typically associated with Clojure style transducers in the JavaScript world. Both are the same things on a higher level. They are again just transformers of stream objects with different interfaces. Though Clojure transducers transform stream consumers, async iterator transducers from this article transform stream producers. A bit more detail can be found here.

换能器的名称通常与JavaScript世界中的Clojure样式换能器相关联。 两者在更高层次上是相同的。 它们再次只是具有不同接口的流对象的转换器。 尽管Clojure转换器可转换流使用者,但本文中的异步迭代器转换器可转换流产生者。 在这里可以找到更多细节。

扩展名 (Extensions)

I'm now working on a transpiler for embedding effects in JavaScript. It can handle ECMAScript async, generators and async generators function syntax extensions to overload default behavior.

我现在正在研究将效果嵌入JavaScript的编译器。 它可以处理ECMAScript异步,生成器和异步生成器功能语法扩展,以重载默认行为。

In fact, the transpiled demo above was built with it. Unlike similar tools like regenerator, it is abstract. Any other effect can be seamlessly embedded in the language using a library implementing its abstract interface. This can significantly simplify JavaScript programs.

实际上,上面编译的演示就是使用它构建的。 与类似再生器之类的工具不同,它是抽象的。 使用实现其抽象接口的库,可以将任何其他效果无缝地嵌入到语言中。 这可以大大简化JavaScript程序。

For example, possible applications are:

例如,可能的应用是:

  • faster standard effects,

    更快的标准效果,
  • saving current execution to a file or DB and restore on a different server or recover after hardware failure,

    将当前执行保存到文件或数据库,并在其他服务器上还原,或者在发生硬件故障后恢复,
  • move control between front-end and back-end,

    在前端和后端之间移动控制,
  • on changing input data, re-execute only relevant part of the program, use transactions, apply logical programming techniques, even Redux principles for async generators may be recovered.

    在更改输入数据时,仅重新执行程序的相关部分,使用事务,应用逻辑编程技术,甚至可以恢复异步生成器的Redux原理。

The compiler implementation itself uses the technique described in the article. It uses non-async generators since it doesn’t have any async message source. The approach significantly simplified the previous compiler version done with Visitors. It now has almost a hundred options. Their implementation is almost independent, and it is still simple to read and extend.

编译器实现本身使用本文中介绍的技术。 它使用非异步生成器,因为它没有任何异步消息源。 该方法极大地简化了使用Visitors完成的先前编译器版本。 现在,它有将近一百种选择。 它们的实现几乎是独立的,并且仍然易于阅读和扩展。

翻译自: https://www.freecodecamp.org/news/decoupling-business-logic/

异步解耦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值