Refactoring is perhaps the most underappreciated task in software development. For me, it can be great fun and joyful to transform inedible code into an elegant & maintainable code. In this blog post, I quickly share a small part of a significant refactoring I just completed, and how moving from callbacks to promises helped design a declarative and composable download pipeline.
Disclaimer: This blog post is not promoting Promises per se. For what it’s worth, I share this opinion on the matter.
The project in question here is the Kidiyo iOS app. Kidiyo believes in introducing digital media to young children through constructive, funny, and healthy content. Therefore, we stand to be the one-in-all platform with all sorts of different items, such as games, videos, ebooks, and offscreen connections.
Being a platform means that the items we provide must be made available and improved dynamically, without the need for new app releases. Currently, those items are downloaded to be played, except for streamable videos and music.
In the new version of Kidiyo, soon to be released, I had three requirements for the downloads:
- The download pipeline should be declarative so that a non-techie can quickly grasp what it does
- Downloading items from different sources should be abstracted and unified
- The app should be able to pick up on an ongoing download after submitting it
This post focused mostly on the first requirement, followed by the second. The third is subject to a new post.
The app supported downloading items from two different sources.
The snippet below is an exact copy of one of the methods responsible for downloading items from one of the sources, in this case from Firebase. The method responsible for downloading items from the other source was remarkably identical in structure, differing in details on how those items were unpacked and stored locally.
There are many things wrong here:
- It’s imperative
- It’s a big callback hell
- It’s not composable nor sanely extendable
- It has something like 9 levels of nesting
- It’s a dump of responsibilities from different levels of abstraction
- The success and the error flows are far away, therefore harder to visualise
Functional principles to the rescue. The way was to break down all the different
sub-tasks taking place all together into separate, self-sufficient units that always
returned either a token for a next step or an Error. Very similar to
but with the requirement to be run async. That way, I could design the download
pipeline declaratively, where each part of the chain can be put it an out effortlessly.
The result is the following:
Note: I use the freshOS/then Promises Library for Swift with Async/Await.