-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathavoiding-callback-hell-in-swift.html
More file actions
504 lines (455 loc) · 30.1 KB
/
avoiding-callback-hell-in-swift.html
File metadata and controls
504 lines (455 loc) · 30.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://use.fontawesome.com/afd448ce82.js"></script>
<!-- Meta Tag -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- SEO -->
<meta name="author" content="Bruno Rocha">
<meta name="keywords" content="Software, Engineering, Blog, Posts, iOS, Xcode, Swift, Articles, Tutorials, OBJ-C, Objective-C, Apple">
<meta name="description" content="Being able to work in projects of the most diverse varieties gave me the chance to be in contact with several types of developers and code bases. Besides their core differences, what stood out to me during this process is that projects with a lower level of maturity will always face similar problems.">
<meta name="title" content="Avoiding Callback Hell in Swift">
<meta name="url" content="https://swiftrocks.com/avoiding-callback-hell-in-swift">
<meta name="image" content="https://swiftrocks.com/images/thumbs/thumb_dark.jpg">
<meta name="copyright" content="Bruno Rocha">
<meta name="robots" content="index,follow">
<meta property="og:title" content="Avoiding Callback Hell in Swift"/>
<meta property="og:image" content="https://swiftrocks.com/images/thumbs/thumb_dark.jpg"/>
<meta property="og:description" content="Being able to work in projects of the most diverse varieties gave me the chance to be in contact with several types of developers and code bases. Besides their core differences, what stood out to me during this process is that projects with a lower level of maturity will always face similar problems."/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://swiftrocks.com/avoiding-callback-hell-in-swift"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="https://swiftrocks.com/images/thumbs/thumb_dark.jpg"/>
<meta name="twitter:image:alt" content="Page Thumbnail"/>
<meta name="twitter:title" content="Avoiding Callback Hell in Swift"/>
<meta name="twitter:description" content="Being able to work in projects of the most diverse varieties gave me the chance to be in contact with several types of developers and code bases. Besides their core differences, what stood out to me during this process is that projects with a lower level of maturity will always face similar problems."/>
<meta name="twitter:site" content="@rockbruno_"/>
<meta name="fediverse:creator" content="@rockbruno@hachyderm.io">
<!-- Favicon -->
<!-- 16x16 -->
<link rel="shortcut icon" href="images/favicon/favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="images/favicon/favicon_180.png" sizes="180x180">
<link rel="icon" href="images/favicon/favicon_32.png" sizes="32x32" type="image/png">
<link rel="icon" href="images/favicon/favicon_48.png" sizes="48x48" type="image/png">
<link rel="icon" href="images/favicon/favicon_96.png" sizes="96x96" type="image/png">
<link rel="icon" href="images/favicon/favicon_144.png" sizes="144x144" type="image/png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap" rel="stylesheet">
<link rel="canonical" href="https://swiftrocks.com/avoiding-callback-hell-in-swift"/>
<!-- Bootstrap CSS Plugins -->
<link rel="stylesheet" type="text/css" href="css/bootstrap.css">
<!-- Prism CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/prism5.css">
<!-- Main CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/style49.css">
<link rel="stylesheet" type="text/css" href="css/sponsor5.css">
<!-- HTML5 shiv and Respond.js support IE8 or Older for HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://swiftrocks.com/avoiding-callback-hell-in-swift"
},
"image": [
"https://swiftrocks.com/images/thumbs/thumb_dark.jpg"
],
"datePublished": "2018-12-17T13:42:07+00:00",
"dateModified": "2020-04-12T14:00:00+02:00",
"author": {
"@type": "Person",
"name": "Bruno Rocha"
},
"publisher": {
"@type": "Organization",
"name": "SwiftRocks",
"logo": {
"@type": "ImageObject",
"url": "https://swiftrocks.com/images/thumbs/thumb_dark.jpg"
}
},
"headline": "Avoiding Callback Hell in Swift",
"abstract": "Being able to work in projects of the most diverse varieties gave me the chance to be in contact with several types of developers and code bases. Besides their core differences, what stood out to me during this process is that projects with a lower level of maturity will always face similar problems."
}
</script>
</head>
<body>
<div id="main">
<!-- Blog Header -->
<!-- Blog Post (Right Sidebar) Start -->
<div class="container">
<div class="col-xs-12">
<div class="page-body">
<div class="row">
<div><a class="logo-link" href="https://swiftrocks.com">
<img id="logo" class="logo" alt="SwiftRocks" src="images/bg/logo2dark.png">
</a>
<div class="menu-large">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-large">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
<div class="menu-small">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-1">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-2">
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
</div>
<div class="content-page" id="WRITEIT_DYNAMIC_CONTENT">
<!--WRITEIT_POST_NAME=Avoiding Callback Hell in Swift-->
<!--WRITEIT_POST_HTML_NAME=avoiding-callback-hell-in-swift-->
<!--WRITEIT_POST_SITEMAP_DATE_LAST_MOD=2020-04-12T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE=2018-12-17T13:42:07+00:00-->
<!--Add here the additional properties that you want each page to possess.-->
<!--These properties can be used to change content in the template page or in the page itself as shown here.-->
<!--Properties must start with 'WRITE_IT_POST'.-->
<!--Writeit provides and injects WRITEIT_POST_NAME and WRITEIT_POST_HTML_NAME by default.-->
<!--WRITEIT_POST_SHORT_DESCRIPTION=Being able to work in projects of the most diverse varieties gave me the chance to be in contact with several types of developers and code bases. Besides their core differences, what stood out to me during this process is that projects with a lower level of maturity will always face similar problems.-->
<title>Avoiding Callback Hell in Swift</title>
<div class="blog-post">
<div class="post-title-index">
<h1>Avoiding Callback Hell in Swift</h1>
</div>
<div class="post-info">
<div class="post-info-text">
Published on 17 Dec 2018
</div>
</div>
<p>Being able to work in projects of the most diverse varieties gave me the chance to be in contact with several types of developers and code bases. Besides their core differences, what stood out to me during this process is that projects with a lower level of maturity will always face similar problems.</p>
<div class="sponsor-article-ad-auto hidden"></div>
<p>Perhaps they choose the wrong architecture, or the lack of unit tests caused a nasty bug to sneak into production, but there's a specific problem that always draws my attention - callback hell. If not treated from the very beginning, these awful pyramids of braces you get when chaining callbacks inside other callbacks or conditions plague code bases with an eternity of impossible code-reviews and distant screams of "what the hell is this method supposed to be doing?".</p>
<pre><code>private func requestNetwork<T: Request>(request: T, completion: (Result<T> -> Void)?) {
if isUserLogged {
do {
let urlRequest = try request.toRequest()
session.dataTask(with: urlRequest) { (data, response, error) in
if let httpResponse = response as? HTTPURLResponse {
if acceptedStatuses?.contains(httpResponse.statusCode) != true {
if let apiError = errorParser?.possibleError(from: data) {
completion(.failure(error))
return
}
}
}
preprocess(data) { (processedData, error) in
if let error = error {
completion(.failure(error))
}
if let processedData = processedData {
do {
let result = try request.serialize(processedData)
completion(.success(result))
} catch {
completion(.failure(error))
}
} else {
completion(.failure(HTTPError.unknown))
}
}
}
} catch {
completion(.failure(error))
}
} else {
completion(.failure(HTTPError.loggedOut))
}
}</code></pre>
<p>They are difficult to read, nearly impossible to review but unfortunately super easy to write, cementing their place as the bane of junior developers.</p>
<p>Fortunately for us, Swift offers several options to avoid this behavior. With a bit of patience and a proper style guide, you can prevent this sort of mistake from affecting your productivity. I'll use this article to share how I personally avoid them, and hopefully this will help you come up with your own solutions.</p>
<h2>Condition hells: Favor using guard instead of if</h2>
<p>Pyramids of conditions are very common, and fortunately the easier to deal with. <code>guard</code> is on my top 10 features in Swift for a good reason - although it works basically as an inverted <code>if</code> statement, it gives you a great advantage in terms of code quality. Besides providing a way for you to give an early return to a method, it allows you to put the "good" outcome of a method in the same indentation as the method itself, making your method's intent far easier to be understood by your colleague. The improvement is not difficult to spot in a chain of <code>if</code> statement:</p>
<pre><code>func foo() {
if a {
if b {
if c {
//Good outcome
} else {
//Bad outcome 3
}
} else {
//Bad outcome 2
}
} else {
//Bad outcome 1
}
}</code></pre>
<pre><code>func foo() {
guard a else {
return //Bad outcome 1
}
guard b else {
return //Bad outcome 2
}
guard c else {
return //Bad outcome 3
}
//Good outcome
}</code></pre>
<p>If you embrace the mindset of putting the good outcome of your method as close as possible to the method's indentation and the bad outcomes as far as possible from it, you'll find your code to be significantly easier to read as a mere glance at the end of the method will be enough for someone to understand what it's supposed to do. Use <code>guards</code> to isolate things that are not supposed to happen and restrict the usage of <code>ifs</code> to things that aren't necessary for the good outcome to happen, like changing the color of a cell based on a property's value.</p>
<pre><code>func updatePromotions(animated: Bool = true) {
guard isUserLogged else {
displayLoginScreen()
return
}
if animated {
delegate?.didStartLoading()
}
//Good outcome: Fetch promotions
}</code></pre>
<h2>Closure hells: Abstracting completion handlers</h2>
<p>Callback hells caused by asynchronous calls are the trickier to solve as completion handlers can contain pretty much anything, but there are efficient ways to deal with them as well.</p>
<h3>Promises</h3>
<p>The concept of Promises is my go-to solution for managing anything that's asynchronous. If you have never seen them before, Promises relate to the concept of a type that may or may not resolve a value at a later time:</p>
<pre><code>func getInt() -> Promise<Int>
return Promise { promise in
//Do something async
promise.fulfill(number)
//Or promise.fail(error)
}
}
let promise = getInt().then { number in
print(number * 10) //if it succeeds
}.catch { error in
print(error) //if it fails
}</code></pre>
<p>The <code>Promise</code> type can receive closures that determine how to proceed depending on the result of resolving the value, represented by <code>then(completion:)</code> and <code>catch(completion:)</code> in this case. If you're wondering why this helps with callback hells, it's because <code>then</code> handlers can optionally receive <b>another promise</b>, creating a limitless straight flow of operations:</p>
<pre><code>func perform<T: Request>(request: T) -> Promise<T.Response>
return Promise { promise in
//Do the actual request here, then:
promise.fulfill(response)
}
}
perform(requestOne()).then { responseOne in
perform(requestTwo(responseOne: responseOne))
}.then { responseTwo in
perform(requestThree(responseTwo: responseTwo))
}.then { responseThree in
perform(requestFour(responseThree: responseThree))
}.catch { error in
print(error)
}.always {
print("Finished")
}</code></pre>
<p>By making your async operations return <code>Promise</code> types instead of receiving completion handlers, you will be able to chain any amount of them together into a nice straight line of code. They are specially great when your operations depend on things returned by previous operations as more powerful Promise implementations will contain several options for transforming values as well.</p>
<p>I personally use <a href="https://github.com/mxcl/PromiseKit">PromiseKit</a> as it contains tons of features, <a href="https://github.com/khanlou/Promise">but there are lightweight libraries around the web</a> and you could certainly <a href="https://www.swiftbysundell.com/posts/under-the-hood-of-futures-and-promises-in-swift">develop a simple Promise implementation yourself.</a></p>
<p>You'll see people recommending things like RxSwift for this purpose as well - I would personally not do so because I think that anything that holds your entire project hostage is a death sentence in the long term (as in, every single thing you do has to take RxSwift's architecture in mind in order to work), but that's my personal opinion and you can definitely use it if you know what you're doing.</p>
<h3>"I don't want to add more code!": <code>OperationQueue</code></h3>
<p>If Promises aren't your thing because you'd rather solve things the Apple way, you can use <code>Foundation</code>'s native solutions for managing sequential operations.</p>
<p><code>OperationQueue</code> is Apple's abstraction of <code>DispatchQueue</code> that contains additional features to better support synchronizing and canceling operations. If your operations don't rely on data from previous operations, the <code>Operation</code> family of classes will do the trick. For synchronous operations, this is just a matter of queuing your custom operations:</p>
<pre><code>let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
func doLotsOfStuff(completion: (() -> Void)?) {
let firstOperation: Operation = FirstOperation()
let secondOperation: Operation = SecondOperation()
secondOperation.completionBlock = {
completion?()
}
secondOperation.addDependency(firstOperation)
queue.addOperation(firstOperation)
queue.addOperation(secondOperation)
}</code></pre>
<p>However, things are trickier for asynchronous operations. To make the queue wait for your operation to truly finish, you'll either have to use thread-blocking mechanisms such as <code>DispatchGroups</code> or <a href="https://gist.github.com/Sorix/57bc3295dc001434fe08acbb053ed2bc">create/use a custom AsynchronousOperation type</a> that manages an <code>Operation</code>'s states for this purpose.</p>
<p>If you need an operation to pass data to another one, you'll find no clean solution with <code>OperationQueue</code> as there's no guarantee that an operation's <code>completionBlock</code> will be called before the next one starts running. There a few hacks to achieve them through - you can wrap all your needed data in an external reference type that is accessible by all operations:</p>
<pre><code>func doLotsOfStuff(completion: (() -> Void)?) {
let data = NeededOperationData()
// data has all properties needed by all operations
// and each operation fetches and sets the ones
// needed by the next operation.
let one = OperationOne(data)
let two = OperationTwo(data)
two.addDependency(one)
let three = OperationThree(data)
three.addDependency(two)
queue.addOperation(one)
queue.addOperation(two)
queue.addOperation(three)
}</code></pre>
<p>Alternatively, you can store the necessary data in the operation's dependency and access it by subclassing the operation and fetching its dependencies when it gets executed.</p>
<pre><code>class SecondOperation: AsynchronousOperation {
var data: Data?
override func main() {
super.main()
let firstOperation = dependencies.first as? FirstOperation
data = firstOperation.result
//Run the operation
}
}</code></pre>
<p>I dislike having to deal with optional properties everywhere, so I personally wouldn't use <code>OperationQueue</code> if my operations depended on data fetched by other operations.</p>
<h3>"Foundation sucks!": Use high-order functions</h3>
<p>If you want to do this without using additional data structures, you can treat callback hell with nothing but pure Swift by applying better coding practices and some principles from functional programming. Because closures are types, they can be passed as arguments to methods - normally as completion handlers. The thing is that Swift methods are just glorified closures, so you can pass <b>an entire method as a closure argument</b>. This exact concept can be used to reduce the amount of nested closures in a method:</p>
<pre><code>let sum = array.reduce(0, +)
//reduce() here is an ((Int, ((Int, Int) -> Int)) -> Int)
//and the + operator is func +(lhs: Int, rhs: Int) -> Int,
//... or ((Int, Int) -> Int), so there's no need to define reduce's closure.</code></pre>
<p>To see how this applies, let's assume that we have a method that downloads a picture from the web, locally applies a Sepia tone filter to it in another thread and then uploads it as the user's profile picture:</p>
<pre><code>func applySepiaFilterAndUpload(picUrl: URL, completion: ((User) -> Void)?) {
session.perform(downloadPictureRequest(url: picUrl)) { data in
filtersQueue.async {
let filteredPicture = applySepiaFilterTo(picData: data)
session.perform(uploadUserPictureRequest(data: filteredPicture)) { user in
completion?(user)
}
}
}
}</code></pre>
<p>I've left out any kind of error management to make this article easier to grasp, but as any classic callback hell problem, the first problem here is clear: this method does way too much stuff. Before we start thinking about the closures, let's first apply the single responsibility principle and divide each part of this workflow into separate methods:</p>
<pre><code>func downloadPicture(fromUrl url: URL, completion: ((Data) -> Void)?) {
session.perform(downloadPictureRequest(url: url)) { data in
completion?(data)
}
}
func applySepiaFilter(toPicData data: Data, completion: ((Data) -> Void)?) {
filtersQueue.async {
let filteredPicture = applySepiaFilterTo(picData: data)
completion?(filteredPicture)
}
}
func uploadUserPicture(data: Data, completion: ((User) -> Void)?) {
session.perform(uploadUserPictureRequest(data: data)) { user in
completion?(user)
}
}
func applySepiaFilterAndUpload(picUrl: URL, completion: ((User) -> Void)?) {
downloadPicture(fromUrl: picUrl) { data in
applySepiaFilter(toPicData: data) { filtered in
uploadUserPicture(data: filtered) { user in
completion?(user)
}
}
}
}</code></pre>
<p>Although the callback hell still exists here, we at least have something that's readable now.</p>
<p>To reduce the amount of nested closures, analyze how this method works. Can you see the pattern in <code>applySepiaFilterAndUpload()</code>? The key to solving the nesting problem here is how each step works: every method here works in the exact same way. <code>downloadPicture</code> receives an <code>URL</code> and provides a <code>Data</code> completion, <code>applySepiaFilter</code> receives a <code>Data</code> and provides another <code>Data</code> completion, and <code>uploadUserPicture</code> receives a <code>Data</code> and provides a <code>User</code> completion. If you turn these types into generics, you'll end up with:</p>
<pre><code>downloadPicture = (T, (U -> Void)) -> Void
applySepiaFilter = (U, (V -> Void)) -> Void
uploadUserPicture = (V, (W -> Void)) -> Void</code></pre>
<p>Because these async operations have the exact same structure and clearly depend on each other, we can completely remove the necessity of having closures by adapting these methods to recieve the next one as an argument. This would be trivial to do if each method had an explicit return type, but since we're dealing with completion handlers we need to write a little helper to achieve this effect. First, I'll define this shared behaviour as an <code>Operation</code> alias (with optionals so nobody's forced to do anything):</p>
<pre><code>public typealias Operation<T, U> = ((T, ((U) -> Void)?) -> Void)?</code></pre>
<p>With that, we can define a method that "merges" two operations into one as long as they have matching parameters - making <code>(T, (U -> Void)) -> Void</code> + <code>(U, (V -> Void)) -> Void</code> become <code>(T, (V -> Void)) -> Void</code>:</p>
<pre><code>func merge<T, U, V>(_ lhs: Operation<T, U>, to rhs: Operation<U, V>) -> Operation<T, V> {
return { (input, completion) in
lhs?(input) { output in
rhs?(output, completion)
}
}
}</code></pre>
<p>This method returns a <b>new closure</b> that performs the first operation method with a given input, uses its output to execute the second one and finally executes a given completion for the second operation's result. If all our methods follow the <code>Operation</code> structure, we can use <code>merge()</code> to progressively merge all steps into a single operation. We can't really escape the nesting in this helper, but this allows us to rewrite our main method without them:</p>
<pre><code>func applySepiaFilterAndUpload(picUrl: URL, completion: ((User) -> Void)?) {
let job = merge(merge(downloadPicture, to: applySephiaFilter), to: uploadUserPicture)
job?(picUrl, completion)
}</code></pre>
<p>Because the signature of our operations match <code>merge()</code>'s closure arguments, we can skip having to define closures by passing the methods' signatures as the arguments. In the end, <code>job</code> becomes an unified method that takes an <code>URL</code>, executes all operations in order and then finally the executes the method's completion handler with the result of the last operation. That's just like the first version, but with no nesting at all!</p>
<p>Now, if you're thinking <i>"but that looks terrible!"</i>, you're absolutely right. Because we can only merge two operations at a time, we need to call <code>merge()</code> several times which will result in something that's probably harder to read than the original callback hell. There's a way to fix this though - we can define an operator for <code>merge()</code>'s behavior: </p>
<pre><code>infix operator >>->>: LogicalConjunctionPrecedence // Precedence of &&
func >>->><T, U, V>(lhs: Operation<T, U>, rhs: Operation<U, V>) -> Operation<T, V> {
return merge(lhs, rhs)
}</code></pre>
<p>By using <code>&&</code>'s precedence, operations will be progressively merged all the way from the left. With that in place, we can now rewrite our workflow as a nice straight line of operations.</p>
<pre><code>func applySepiaFilterAndUpload(picUrl: URL, completion: ((User) -> Void)?) {
let job = downloadPicture >>->> applySepiaFilter >>->> uploadUserPicture
job?(picUrl, completion)
}</code></pre>
<p>If you're into this sort of stuff, the formal name for this very specific merging operation is the <a href="https://blog.ssanj.net/posts/2017-06-07-composing-monadic-functions-with-kleisli-arrows.html">Kleisi composition.</a></p>
<h2>Conclusion: Read articles and books about writing clean code</h2>
<p>If you take a deep look at it, you'll notice that the presence of things like callback hell will always boil down to the lack of good coding practices.</p>
<div class="sponsor-article-ad-auto hidden"></div>
<p>Clean code is a big topic, but there are great resources about it around the web. I've personally read and highly recommend <a href="https://www.amazon.com/dp/B001GSTOAM/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1">Robert C. Martin's Clean Code book</a> as it teaches you how to see your code from the perspective of other developers - a great skill to have when learning how to write better looking code. You should definitely give it a try if you're a professional developer.</p>
<p>Follow me on my Twitter - <a href="https://twitter.com/rockbruno_">@rockbruno_</a>, and let me know of any suggestions and corrections you want to share.</p>
<h2>References and Good reads</h2>
<a href="https://en.wikipedia.org/wiki/Futures_and_promises">Promises</a>
<br>
<a href="https://blog.ssanj.net/posts/2017-06-07-composing-monadic-functions-with-kleisli-arrows.html">Kleisi composition</a>
<br>
<a href="https://developer.apple.com/videos/play/wwdc2015/226/">WWDC: Advanced NSOperations</a>
<br>
<a href="https://gist.github.com/Sorix/57bc3295dc001434fe08acbb053ed2bc">AsynchronousOperation.swift</a>
</div></div>
<div class="blog-post footer-main">
<div class="footer-logos">
<a href="https://swiftrocks.com/rss.xml"><i class="fa fa-rss"></i></a>
<a href="https://hachyderm.io/@rockbruno"><svg class="svg-logo-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M433 179.1c0-97.2-63.7-125.7-63.7-125.7-62.5-28.7-228.6-28.4-290.5 0 0 0-63.7 28.5-63.7 125.7 0 115.7-6.6 259.4 105.6 289.1 40.5 10.7 75.3 13 103.3 11.4 50.8-2.8 79.3-18.1 79.3-18.1l-1.7-36.9s-36.3 11.4-77.1 10.1c-40.4-1.4-83-4.4-89.6-54a102.5 102.5 0 0 1 -.9-13.9c85.6 20.9 158.7 9.1 178.8 6.7 56.1-6.7 105-41.3 111.2-72.9 9.8-49.8 9-121.5 9-121.5zm-75.1 125.2h-46.6v-114.2c0-49.7-64-51.6-64 6.9v62.5h-46.3V197c0-58.5-64-56.6-64-6.9v114.2H90.2c0-122.1-5.2-147.9 18.4-175 25.9-28.9 79.8-30.8 103.8 6.1l11.6 19.5 11.6-19.5c24.1-37.1 78.1-34.8 103.8-6.1 23.7 27.3 18.4 53 18.4 175z"/></svg></a>
<a href="https://twitter.com/rockbruno_"><i class="fa fa-twitter"></i></a>
<a href="https://github.com/rockbruno"><i class="fa fa-github"></i></a>
</div>
<div class="footer-text">
© 2025 Bruno Rocha
</div>
<div class="footer-text">
<p><a href="https://swiftrocks.com">Home</a> / <a href="blog">See all posts</a></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Blog Post (Right Sidebar) End -->
</div>
</div>
</div>
<!-- All Javascript Plugins -->
<script type="text/javascript" src="js/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/prism5.js"></script>
<!-- Main Javascript File -->
<script type="text/javascript" src="js/scripts30.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-H8KZTWSQ1R"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-H8KZTWSQ1R');
</script>
</body>
</html>