MVP: Minimum Viable Promise
Tags: ios, async, promise, gcd
Published: 2021-03-21 13:49:56

This has been a long and busy week, but I didn't want to skip my weekly article here on Dev.to, so let me show you something I like to do when I'm bored: I open a Playground and create a simple promise implementation or a networking layer.

Let me introduce to you the minimum and most elegant promises implementation I've created so far.

The code is here:

```swift
struct Promise<T> {
    typealias ResultType = Result<T, Error>
    typealias ResultObserver = (ResultType) -> Void
    typealias CreatorFunction = (@escaping ResultObserver) -> Void
    private let creatorFunction: CreatorFunction

    init(creatorFunction: @escaping CreatorFunction) {
        self.creatorFunction = creatorFunction
    }

    func then<E>(_ f: @escaping (T) -> E) -> Promise<E> {
        return Promise<E> { observer in
            self.creatorFunction { r in
                observer(r.map(f))
            }
        }
    }

    static func all(_ promises: [Promise<T>], on queue: DispatchQueue = .main) -> Promise<[T]> {
        return Promise<[T]> { observer in
            let group = DispatchGroup()
            var values = [T]()
            var hasFailed = false
            for promise in promises {
                group.enter()
                promise.then { r in
                    switch r {
                    case let .success(value):
                        values.append(value)
                    case let .failure(error):
                        observer(.failure(error))
                        hasFailed = true
                    }
                    group.leave()
                }
            }
            group.notify(queue: queue) {
                guard !hasFailed else {
                    return
                }
                observer(.success(values))
            }
        }
    }

    func then<E>(_ f: @escaping (T) -> Promise<E>) -> Promise<E> {
        return Promise<E> { observer in
            self.creatorFunction { firstResult in
                switch firstResult {
                case .success(let successResult):
                    f(successResult).creatorFunction { transformedResult in
                        observer(transformedResult)
                    }
                case .failure(let error):
                    observer(.failure(error))
                }
            }
        }
    }

    @discardableResult func then(_ f: @escaping (ResultType) -> Void) -> Self {
        creatorFunction { r in f(r) }
        return self
    }
}
```

# What is a Promise?

A `Promise`, or [`Future`](https://developer.apple.com/documentation/combine/future) in `Combine`, represents a value that isn't there yet, but you know that it will be there at some point in the future.
For example, if you're performing a network request, a `Promise` will tell you "ok, so you've done a network request. I don't have the response yet and it will probably take a couple of seconds, but tell me what you'd like to do with it, and I will do it for you when I have the response". Basically, that's it.

# What is the difference with callbacks?

You've probably seen this picture a million times

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/as2zk3ax9fuqxgmt4bt9.png)

This is javascript in this case, but it works also for Swift.  When doing callbacks, the biggest problem you'll have is that chaining async operations will be painful. [I've written in the past](https://dev.to/fmo91/dispatchgroup-in-swift-gg7) about running async operations in parallel. Using `DispatchGroup` is a way to solve the problems of fixing when operations need to be executed in parallel and are performed async.

Promises can also do that, leveraging GCD mechanisms under the hood. Plus, they solve the problem of chaining sequences of async operations.

Promises scale much much better than callbacks, and are relatively simple to use once you get used to its idioms.

# How does this work?

In the implementation I pasted above, I implemented a couple of main functions:

### `init(creatorFunction: @escaping CreatorFunction)`

In the `init` I'm just looking for a `CreatorFunction` which at the moment of be used, you can do it this way:

```swift
func getNumber() -> Promise<Int> {
    return .init { (observer) in
        observer(.success(5))
    }
}
```

What is that `CreatorFunction`? It's the heart of a `Promise`:

```swift
struct Promise<T> {
    typealias ResultType = Result<T, Error>
    typealias ResultObserver = (ResultType) -> Void
    typealias CreatorFunction = (@escaping ResultObserver) -> Void
    private let creatorFunction: CreatorFunction

    init(creatorFunction: @escaping CreatorFunction) {
        self.creatorFunction = creatorFunction
    }

    // ...
}
```

A `Promise` is not much more than a struct that holds a function (let's call it the **creator**). That function is created taking another function (the **observer**) as an argument. The observer is a function that can be used to report the result of the creator. And that result is a Swift `Result` type.
This takes some time to sink, but when it does, you have understood what a `Promise` is and how to create one.

### `func then<E>(_ f: @escaping (T) -> E) -> Promise<E>`

Does this look familiar to you? It's a `map`. We are transforming a `Promise<T>` to a `Promise<E>` using a simple function `(T) -> E`. 

```swift
func then<E>(_ f: @escaping (T) -> E) -> Promise<E> {
    return Promise<E> { observer in
        self.creatorFunction { r in
            observer(r.map(f))
        }
    }
}
```

This is very simple, just creating a new `Promise` and "unbox" the result from the `creatorFunction`.

### `func then<E>(_ f: @escaping (T) -> Promise<E>) -> Promise<E>`

And this one? It's a `flatMap`. We're converting a `Promise<T>` to a `Promise<E>` using a function that isn't that simple this time, because it returns a `Promise<E>` and not a simple `E`.

The implementation is as follows:

```swift
func then<E>(_ f: @escaping (T) -> Promise<E>) -> Promise<E> {
    return Promise<E> { observer in
        self.creatorFunction { firstResult in
            switch firstResult {
            case .success(let successResult):
                f(successResult).creatorFunction { transformedResult in
                    observer(transformedResult)
                }
            case .failure(let error):
                observer(.failure(error))
            }
        }
    }
}
``` 

The key here is to use the `creatorFunction` manually in the newly created `Promise`.

### `static func all(_ promises: [Promise<T>], on queue: DispatchQueue = .main) -> Promise<[T]>`

I'll let this one to you. It's basically a `DispatchGroup` that notifies once all the Promises have finished resolving.

```swift
static func all(_ promises: [Promise<T>], on queue: DispatchQueue = .main) -> Promise<[T]> {
    return Promise<[T]> { observer in
        let group = DispatchGroup()
        var values = [T]()
        var hasFailed = false
        for promise in promises {
            group.enter()
            promise.then { r in
                switch r {
                case let .success(value):
                    values.append(value)
                case let .failure(error):
                    observer(.failure(error))
                    hasFailed = true
                }
                group.leave()
            }
        }
        group.notify(queue: queue) {
            guard !hasFailed else {
                return
            }
            observer(.success(values))
        }
    }
}
```

### `@discardableResult func then(_ f: @escaping (ResultType) -> Void) -> Self`

And, finally, the `@discardableResult` function `then` that basically lets us use the `Promise`

```swift
@discardableResult func then(_ f: @escaping (ResultType) -> Void) -> Self {
    creatorFunction { r in f(r) }
    return self
}
```

# To sum up

This has been a very short and concise tutorial on how to implement a Promise<T>. I'd like to encourage you to open a playground and try to implement async mechanisms like Promise<T>, or event streams like `Observable<E>` or Combine publishers. This could be a greatly rewarding exercise.