異步解耦
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解析為具有value
, done
字段的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.
異步生成器結合了異步函數和生成器的功能。 在此類函數的主體中,我們可以將await
與yield
表達式一起使用,它們分別在異步函數和生成器中分別執行這些表達式。 也就是說,它們將暫停執行控制,直到解析了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.
這是一個有效的示例,請按“ 結果”以查看它的實際效果。 您可以在頁面中拖動四個元素。 主要組成部分是send
, produce
和consume
。 該應用程序訂閱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
功能是應用程序業務邏輯的入口點。 該函數在模塊的頂層之間在produce
和consume
之間調用。
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
完全定義了業務邏輯的第一個整體版本。 這是第一個骯臟的草稿版本,但是異步生成器的功能已經可見。 應用程序狀態(我們開始拖動的位置x
, y
變量)只是封裝在函數內的簡單局部變量。
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
.
除了數據狀態外,還有執行控制狀態。 這是一種隱式局部變量存儲位置,在該位置上生成器被掛起(處于await
或yield
)。 雖然真正的魔術在我們開始分解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))
.
最常用的函數組合是它們的組合:對于函數f
和g
,兩個函數的組合為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位置更新為setPositions
。 main
功能只是兩者的組合。
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
屬性將其添加到所有消息中。 該信息用于trash
和validateOver
傳感器中,以確定是否需要刪除元素或取消拖動。
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
x
和y
局部變量中只有一個開始坐標。 并且其控制流僅針對一個元素定義。 在"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/
異步解耦