GitHub - malcommac/Hydra: Lightweight full-featured Promises, Async & Await...
source link: https://github.com/malcommac/Hydra
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
README.md
Love your async code again with Hydra
Made with ♥ in pure Swift 3.x/4.x, no dependencies, lightweight & fully portable
★★ Star our github repository to help us! ★★
Created by Daniele Margutti (@danielemargutti)
Swift 3 and Swift 4 Compatibility
- Swift 4.x: Latest is 1.2.1 (
pod 'HydraAsync'
) - Swift 3.x: Latest is 1.0.2 - Latest compatible version is 1.0.2 Download here. If you are using CocoaPods be sure to fix the release (
pod 'HydraAsync', '~> 1.0.2'
)
Hydra
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!
Internals
A more detailed look at how Hydra works can be found in ARCHITECTURE file or on Medium.
OTHER LIBRARIES YOU MAY LIKE
I'm also working on several other projects you may like. Take a look below:
Library Description SwiftDate The best way to manage date/timezones in Swift Hydra Write better async code: async/await & promises Flow A new declarative approach to table managment. Forget datasource & delegates. SwiftRichString Elegant & Painless NSAttributedString in Swift SwiftLocation Efficient location manager SwiftMsgPack Fast/efficient msgPack encoder/decoder
Current Release (Swift 3 and 4 releases)
Latest releases are:
- Swift 4.x: Latest is 1.2.0 (
pod 'HydraAsync'
) Download here. - Swift 3.x: Latest is 1.0.1 (
pod 'HydraAsync', '~> 1.0.1'
) Download here.
A complete list of changes for each release is available in the CHANGELOG file.
Index
- What's a Promise
- Updating to >=0.9.7
- Create a Promise
- How to use a Promise
- Chaining Multiple Promises
- Cancellable Promises
- Await & Async: async code in sync manner
- Await an
zip
operator to resolve all promises - All Features
- Chaining Promises with different
Value
types - Installation (CocoaPods, SwiftPM and Carthage)
- Requirements
- Credits
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!
Updating to >=0.9.7
Create a Promise
This is a simple async image downloader:
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 anUIImage
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)
How to use a Promise
Chaining Multiple Promises
Chaining Promises is the next step thought mastering Hydra. Suppose you have defined some Promises:
Cancellable Promises
When isCancelled
is set to true
it means someone outside the promise want to cancel the task.
Your promise must be also initialized using this token instance.
To use this with table cells, the queue should be invalidated and reset on prepareForReuse()
.
Await & Async: async code in sync manner
Have you ever dream to write asynchronous code like its synchronous counterpart? Hydra was heavily inspired by Async/Await specification in ES8 (ECMAScript 2017) which provides a powerful way to write async doe in a sequential manner.
Using async
and await
is pretty simple.
For example the code above can be rewritten directly as:
// With `async` we have just defined a Promise which will be executed in a given // context (if omitted `background` thread is used) and return an Int value. let asyncFunc = async({ _ -> Int in // you must specify the return of the Promise, here an Int // With `await` the async code is resolved in a sync manner let loggedUser = try await(loginUser(username,pass)) // one promise... let followersList = try await(getFollowers(loggedUser)) // after another... let countUnfollowed = try await(unfollow(followersList)) // ... linearly // Then our async promise will be resolved with the end value return countUnfollowed }).then({ value in // ... and, like a promise, the value is returned print("Unfollowed \(value) users") })
Like magic! Your code will run in .background
thread and you will get the result of each call only when it will be fulfilled. Async code in sync sauce!
Important Note: await
is a blocking/synchronous function implemented using semaphore. Therefore, it should never be called in main thread; this is the reason we have used async
to encapsulate it. Doing it in main thread will also block the UI.
async
func can be used in two different options:
- it can create and return a promise (as you have seen above)
- it can be used to simply execute a block of code (as you will see below)
As we said we can also use async
with your own block (without using promises); async
accepts the context (a GCD queue) and optionally a start delay interval.
Below an example of the async function which will be executed without delay in background:
async({ print("And now some intensive task...") let result = try! await(.background, { resolve,reject, _ in delay(10, context: .background, closure: { // jut a trick for our example resolve(5) }) }) print("The result is \(result)") })
There is also an await operator:
- await with throw:
..
followed by a Promise instance: this operator must be prefixed bytry
and should usedo/catch
statement in order to handle rejection of the Promise. - await without throw:
..!
followed by a Promise instance: this operator does not throw exceptions; in case of promise's rejection result is nil instead.
Examples:
async({ // AWAIT OPERATOR WITH DO/CATCH: `..` do { let result_1 = try ..asyncOperation1() let result_2 = try ..asyncOperation2(result_1) // result_1 is always valid } catch { // something goes bad with one of these async operations } }) // AWAIT OPERATOR WITH NIL-RESULT: `..!` async({ let result_1 = ..!asyncOperation1() // may return nil if promise fail. does not throw! let result_2 = ..!asyncOperation2(result_1) // you must handle nil case manually })
When you use these methods and you are doing asynchronous, be careful to do nothing in the main thread, otherwise you risk to enter in a deadlock situation.
The last example show how to use cancellable async
:
func test_invalidationTokenWithAsyncOperator() { // create an invalidation token let invalidator: InvalidationToken = InvalidationToken() async(token: invalidator, { status -> String in Thread.sleep(forTimeInterval: 2.0) if status.isCancelled { print("Promise cancelled") } else { print("Promise resolved") } return "" // read result }).then { _ in // read result } // Anytime you can send a cancel message to invalidate the promise invalidator.invalidate() }
Await an zip
operator to resolve all promises
Await can be also used in conjuction with zip to resolve all promises from a list:
All Features
always
: allows you to specify a block which will be always executed both for fulfill
and reject
of the Promise
validate
: allows you to specify a predica block; if predicate return false
the Promise fails.
timeout
: add a timeout timer to the Promise; if it does not fulfill or reject after given interval it will be marked as rejected.
all
: create a Promise that resolved when the list of passed Promises resolves (promises are resolved in parallel). Promise also reject as soon as a promise reject for any reason.
any
: create a Promise that resolves as soon as one passed from list resolves. It also reject as soon as a promise reject for any reason.
pass
: Perform an operation in the middle of a chain that does not affect the resolved value but may reject the chain.
recover
: Allows recovery of a Promise by returning another Promise if it fails.
map
: Transform items to Promises and resolve them (in paralle or in series)
zip
: Create a Promise tuple of a two promises
defer
: defer the execution of a Promise by a given time interval.
cancel
: cancel is called when a promise is marked as cancelled
using operation.cancel()
always
validate
validate
is a func that takes a predicate, and rejects the promise chain if that predicate fails.
timeout
all
If one Promise fail the chain fail with the same error.
Execution of all promises is done in parallel.
any
pass
recover
recover
allows you to recover a failed Promise by returning another.
map
Map is used to transform a list of items into promises and resolve them in parallel or serially.
zip
defer
retry
Conditional retry allows you to control retryable if it ends with a rejection.
cancel
cancel
is called when a promise is marked as cancelled
from the Promise's body by calling the operation.cancel()
function. See the Cancellable Promises for more info.
asyncFunc1().cancel(.main, { // promise is cancelled, do something }).then...
Chaining Promises with different Value
types
Installation
You can install Hydra using CocoaPods, Carthage and Swift package manager
Swift 3.x: Latest compatible is 1.0.2pod 'HydraAsync', ~> '1.0.2'
Swift 4.x: 1.2.1 or later pod 'HydraAsync'
CocoaPods
use_frameworks!
pod 'HydraAsync'
Carthage
github 'malcommac/Hydra'
Swift Package Manager
Add Hydra as dependency in your Package.swift
import PackageDescription
let package = Package(name: "YourPackage",
dependencies: [
.Package(url: "https://github.com/malcommac/Hydra.git", majorVersion: 0),
]
)
Requirements
Current version is compatible with:
Swift 4 (>= 1.2.1) or Swift 3.x (Up to 1.0.2) iOS 8.0 or later tvOS 9.0 or later macOS 10.10 or later watchOS 2.0 or later Linux compatible environmentsCredits & License
Hydra is owned and maintained by Daniele Margutti.
As open source creation any help is welcome!
The code of this library is licensed under MIT License; you can use it in commercial products without any limitation.
The only requirement is to add a line in your Credits/About section with the text below:
This software uses open source Hydra's library to manage async code.
Web: http://github.com/malcommac/Hydra
Created by Daniele Margutti and licensed under MIT License.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK