麻豆约拍

Taming the Async Beast with FRP and RxJS

Guest Post: how we're orchestrating interdependent asynchronous data streams within our video mixing interface.

Published: 17 July 2017

This is our fifth post on the work we鈥檙e doing with software development agency . They're helping us produce a browser-based vision mixing interface to support low-cost production run by a single operator. You can find out more in their first post and in some of the links below. Isotoma's Alex Holmes has the latest installment in the series.

The Problem

Live vision mixing involves keeping track of a large number of interdependent data streams. Our application receives timing data for video tapes and live video streams via webrtc data channels and websocket connections and we鈥檙e sending video and audio authoring decisions over other websockets to the live rendering backend.

Many of the data streams we鈥檙e handling are interdependent; we don鈥檛 want to send an authoring decision to the renderer to cut to a video tape until the video tape is loaded and ready to play, so we need to wait until the video tape is ready to play before we send an authoring decision; if the authoring websocket has closed we鈥檒l need to reconnect to it then retry sending that authoring decision.

Orchestrating interdependent asynchronous data streams is a fundamentally complex problem.

for composing asynchronous operations and safely transforming the results, however they have a number of limitations. The primary issue is that they cannot be cancelled, so we need to handle teardown separately somehow.

We could use the excellent or Future libraries instead, both of which support cancellation (and are lazy and chainable and compliant), but futures and promises handle one single future value (or error value), not a stream of many values (or error value).

The team working this project are fans of futures (less so of promises) and were aiming to write the majority of the codebase in a functional style using and (and react-redux) so wanted a functional, composable way to handle ongoing streams of data that could sit comfortably within the rest of the codebase.

A Solution

After some debate, we decided to use FRP (functional reactive programming) powered by the observable pattern. Having used RxJS (with ) for smaller projects in the past, we were confident that it could be an elegant solution to our problem.

You can about but, in short, it鈥檚 a library that allows subscribers to listen to and transform the output of a data stream as per the observer pattern, and allows the observable (the thing subscribed to) to 鈥渃omplete鈥 its stream when it runs out of data (or whatever), similar to an iterator from the iterator pattern. Observables also allow their subscribers to terminate them at any point, and typically observables will encapsulate teardown logic related to their data source 鈥 a websocket, long-poll, webrtc data channel, or similar.

RxJS implements the observer pattern in a functional way that allows developers to compose together observables, just as they鈥檇 compose functions or types. RxJS has its roots in functional reactive programming and leverages the power of monadic composition to chain together streams while also ensuring that teardown logic is preserved and handled as you鈥檇 expect.

Did It Work?

Sort of. We spent a lot of our time in a state of low-level fury at RxJS, so much so that .

There are some good bits though:

FRP and the observable pattern are both transformative approaches to writing complex asynchronous javascript code, producing fewer bugs and drastically improving the reusability of our codebase.

RxJS operators can encapsulate extremely complex asynchronous operations and elegantly describe dependencies in a terse, declarative way that leaves no state lying around.

In multiple standups throughout the project we鈥檝e enthusiastically raved about how these operators have turned a fundamentally complex part of our implementation into a two line solution. Sure those two lines usually took a long time to craft and get right, but once working, it鈥檚 difficult to write many bugs in just two lines of code (when compared to the hundreds of lines of imperative code we鈥檇 otherwise need to write if we rolled our own).

That said, RxJS is a functional approach to writing code so developers should expect to incur a penalty if they鈥檙e new to the paradigm as they go from an imperative, object-oriented approach to system design to a functional, data-flow-driven approach instead. There is also a very steep learning curve required to feel the benefits of RxJS as developers familiarise themselves with the toolbox and the idiosyncrasies.

Would We Use It Again?

Despite the , I would still recommend an FRP approach to complex async javascript projects. In future we鈥檒l be trying out most.js to see if it solves the myriad of problems we found with RxJS. If it doesn鈥檛, I鈥檇 consider implementing an improved Observable that keeps its hands off my errors.

It鈥檚 also worth mentioning that we used RxJS with react-redux to handle all redux side-effects. We used to achieve this and it was terrific. We鈥檒l undoubtedly be using redux-observable again.

Rebuild Page

The page will automatically reload. You may need to reload again if the build takes longer than expected.

Useful links

Theme toggler

Select a theme and theme mode and click "Load theme" to load in your theme combination.

Theme:
Theme Mode: