SkillAgentSearch skills...

Hydra

⚡️ Lightweight full-featured Promises, Async & Await Library in Swift

Install / Use

/learn @malcommac/Hydra
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<p align="center" > <img src="banner.png" width=300px alt="Hydra" title="Hydra"> </p> <p align="center"><strong>Lightweight full-featured Promises, Async & Await Library in Swift</strong></p>

What's this?

Hydra is full-featured lightweight library which allows you to write better async code in Swift 3.x/4.x. It's partially based on JavaScript A+ specs and also implements modern construct like await (as seen in Async/Await specification in ES8 (ECMAScript 2017) or C#) which allows you to write async code in sync manner. Hydra supports all sexiest operators like always, validate, timeout, retry, all, any, pass, recover, map, zip, defer and retry.
Starts writing better async code with Hydra!

A more detailed look at how Hydra works can be found in ARCHITECTURE file or on Medium.

❤️ Your Support

Hi fellow developer!
You know, maintaing and developing tools consumes resources and time. While I enjoy making them your support is foundamental to allow me continue its development.

If you are using SwiftLocation or any other of my creations please consider the following options:

Introduction

<a name="whatspromise" />

What's a Promise?

A Promise is a way to represent a value that will exists, or will fail with an error, at some point in the future. You can think about it as a Swift's Optional: it may or may not be a value. A more detailed article which explain how Hydra was implemented can be found here.

Each Promise is strong-typed: this mean you create it with the value's type you are expecting for and you will be sure to receive it when Promise will be resolved (the exact term is fulfilled).

A Promise is, in fact, a proxy object; due to the fact the system knows what success value look like, composing asynchronous operation is a trivial task; with Hydra you can:

  • create a chain of dependent async operation with a single completion task and a single error handler.
  • resolve many independent async operations simultaneously and get all values at the end
  • retry or recover failed async operations
  • write async code as you may write standard sync code
  • resolve dependent async operations by passing the result of each value to the next operation, then get the final result
  • avoid callbacks, pyramid of dooms and make your code cleaner!
<a name="updating097" />

Updating to >=0.9.7

Since 0.9.7 Hydra implements Cancellable Promises. In order to support this new feature we have slightly modified the Body signature of the Promise; in order to make your source code compatible you just need to add the third parameter along with resolve,reject: operation. operation encapsulate the logic to support Invalidation Token. It's just and object of type PromiseStatus you can query to see if a Promise is marked to be cancelled from the outside. If you are not interested in using it in your Promise declaration just mark it as _.

To sum up your code:

return Promise<Int>(in: .main, token: token, { resolve, reject in ...

needs to be:

return Promise<Int>(in: .main, token: token, { resolve, reject, operation in // or resolve, reject, _
<a name="createpromise" />

Create a Promise

Creating a Promise is trivial; you need to specify the context (a GCD Queue) in which your async operations will be executed in and add your own async code as body of the Promise.

This is a simple async image downloader:

func getImage(url: String) -> Promise<UIImage> {
    return Promise<UIImage>(in: .background, { resolve, reject, _ in
        self.dataTask(with: request, completionHandler: { data, response, error in
            if let error = error {
                reject(error)
            } else if let data = data, let response = response as? HTTPURLResponse {
                resolve((data, response))
            } else {
                reject("Image cannot be decoded")
            }
        }).resume()
    })
}

You need to remember only few things:

  • a Promise is created with a type: this is the object's type you are expecting from it once fulfilled. In our case we are expecting an UIImage so our Promise is Promise<UIImage> (if a promise fail returned error must be conform to Swift's Error protocol)
  • your async code (defined into the Promise's body) must alert the promise about its completion; if you have the fulfill value you will call resolve(yourValue); if an error has occurred you can call reject(occurredError) or throw it using Swift's throw occurredError.
  • the context of a Promise define the Grand Central Dispatch's queue in which the async code will be executed in; you can use one of the defined queues (.background,.userInitiated etc. Here you can found a nice tutorial about this topic)
<a name="howtousepromise" />

How to use a Promise

Using a Promise is even easier.
You can get the result of a promise by using then function; it will be called automatically when your Promise fullfill with expected value. So:

getImage(url).then(.main, { image in
	myImageView.image = image
})

As you can see even then may specify a context (by default - if not specified - is the main thread): this represent the GCD queue in which the code of the then's block will be executed (in our case we want to update an UI control so we will need to execute it in .main thread).

But what happened if your Promise fail due to a network error or if the image is not decodable? catch func allows you to handle Promise's errors (with multiple promises you may also have a single errors entry point and reduce the complexity).

getImage(url).then(.main, { image in
	myImageView.image = image
}).catch(.main, { error in
	print("Something bad occurred: \(error)")
})
<a name="chaining" />

Chaining Multiple Promises

Chaining Promises is the next step thought mastering Hydra. Suppose you have defined some Promises:

func loginUser(_ name:String, _ pwd: String)->Promise<User>
func getFollowers(user: User)->Promise<[Follower]>
func unfollow(followers: [Follower])->Promise<Int>

Each promise need to use the fulfilled value of the previous; plus an error in one of these should interrupt the entire chain.
Doing it with Hydra is pretty straightforward:

loginUser(username,pass).then(getFollowers).then(unfollow).then { count in
	print("Unfollowed \(count) users")
}.catch { err in
	// Something bad occurred during these calls
}

Easy uh? (Please note: in this example context is not specified so the default .main is used instead).

<a name="cancellablepromises" />

Cancellable Promises

Cancellable Promises are a very sensitive task; by default Promises are not cancellable. Hydra allows you to cancel a promise from the outside by implementing the InvalidationToken. InvalidationToken is a concrete open class which is conform to the InvalidatableProtocol protocol. It must implement at least one Bool property called isCancelled.

When isCancelled is set to true it means someone outside the promise want to cancel the task.

It's your responsibility to check from inside the Promise's body the status of this variable by asking to operation.isCancelled. If true you can do all your best to cancel the operation; at the end of your operations just call cancel() and stop the workflow.

Your promise must be also initialized using this token instance.

This is a concrete example with UITableViewCell: working with table cells, often the result of a promise needs to be ignored. To do this, each cell can hold on to an InvalidationToken. An InvalidationToken is an execution context that can be invalidated. If the context is invalidated, then the block that is passed to it will be discarded and not executed.

To use this with table cells, the queue should be invalidated and reset on prepareForReuse().

class SomeTableViewCell: UITableViewCell {
    var token = InvalidationToken()

	func setImage(atURL url: URL) {
		downloadImage(url).then(in: .main, { image in
			self.imageView.image = image
		})
	}

	override func prepareForReuse() {
		super.prepareForReuse()
		token.invalidate() // stop current task and ignore result
		token = InvalidationToken() // new token
	}

	func downloadImage(url: URL) -> Promise<UIImage> {
		return Promise<Something>(in: .background, token: token, { (resolve, reject, operation) in
		// ... your async operation

		// somewhere in your Promise's body, for example in download progression
		// you should check for the stat

Related Skills

View on GitHub
GitHub Stars2.1k
CategoryDevelopment
Updated23h ago
Forks115

Languages

Swift

Security Score

100/100

Audited on Mar 23, 2026

No findings