Impose is available under the MIT license. See the LICENSE file for more info.
Basic Usage
Ergo utilize the Thenable protocol which is implemented in the Promise class that acts as a proxy for the concurrent task. To create a concurrent task in Promise, just call the runPromise global method with any task you want to run:
runPromise {
print("I'm running in the DispatchQueue.global(qos: .background)")
}
you can pass DispatchQueue to run on those queue:
runPromise(on: .main) {
print("I'm running in the DispatchQueue.main")
}
Chaining Promises
Promise designed to be chainable with other Promise. To chain it, simply call then after each Promise:
runPromise {
print("I'm running in the DispatchQueue.global(qos: .background)")
}.then {
print("I'm running on the same DispatchQueue as previous")
}.then(on: .main) {
print("I'm running in the DispatchQueue.main")
}
You can chain it as much as you need. All of the chaining Promise will be released from the chain after it’s finished doing its task.
You can also pass a value from one Promise to another so it could be used there:
runPromise {
return "from first promise"
}.then { fromPrevious -> String in
print(fromPrevious)
return "from second promise"
}.then(on: .main) { fromPrevious in
print(fromPrevious)
}
Multiple Thenable
Promise can be handled by multiple Thenable. All you need to do is just call as much Thenable as you need after the particular Promise:
let myPromise = runPromise {
return Bool.random()
}
myPromise.then { result in
guard result else { return }
print("this run when true")
}
myPromise.then { result in
guard !result else { return }
print("this run when false")
}
Handling Error
Promise closure is throwable by default. You could always throw an error in the Promise closure to stop next Thenable to be executed
runPromise {
print("no error here")
}.then {
throw MyError()
}.then(on: .main) {
print("this line will not be executed because previous closure throw an error")
}
You can add error handler closure after then to catch the error and do something with it:
runPromise {
print("no error here")
}.then {
throw MyError()
}.handle {
print($0)
print("this line will executed with error throwed")
}.then(on: .main) {
print("this line will not be executed because previous closure throw an error")
}.handle {
print($0)
print("this line will executed with previous error throwed")
}
The error throws from Promise will always passed into all of its child Promise
Finally Block
Promise have finally block which will always be executed regarding error or not the previous Promise is. It will produce another promise which will called after finally is executed:
runPromise {
print("no error here")
}.then {
throw MyError()
}.then(on: .main) {
print("this line will not be executed because previous closure throw an error")
}.finally { result, error in
print("this line be executed. Result will be nil and error will be MyError")
}.then {
print("this line will be executed after finally block finished")
}.finally { result, error in
print("this line always be executed after all promise is done")
}
Timeout
You can add a timeout to your promise that will automatically throw error if the task is not finished after the given timeout:
runPromise(timeout: 1) {
doLongTask()
}.then {
print("task is run for less than 1 second")
}.handle {
print("task is run for more than 1 second")
}
Droping a promise
Promise can be dropped by calling the drop method. It will then emit an error and skip the current task if not finished yet.
You can always pass custom errors when dropping so it will emit that error instead of the default one.
let promise = runPromise {
print("will be dropped")
}
promise.drop()
Keep in mind that this will only drop the current Promise. finally block and handle block will still be called:
let promise = runPromise {
print("will not be dropped")
}.then {
print("will be dropped")
}.handle { error in
print("will still be executed")
}.finally { result, error in
print("will still be executed")
}
promise.drop()
Continue with other Promise
You can continue then with new promise. Use thenContinue instead of then, then return a Promise:
runPromise {
// do something
}.thenContinue {
return somethingThatReturnAPromise()
}.then {
print("will executed after promise from somethingThatReturnAPromise() is finished"
}
Combining Promises
You can combine up to 3 Promise to be a single Promise of Tuple as a Result:
let firstPromise = runPromise {
return "from first Promise"
}
let secondPromise = runPromise {
return "from second Promise"
}
waitPromises(from: firstPromise, secondPromise).then { result in
// will print "from first Promise, from second Promise"
print("\(result.1),\(result.2)")
}
Since waitPromises actually just return back a Promise of Tuple, you can always treat it as regular Promise
Promise status
You can always check the Promise status using its object. its have some properties you can check:
currentValue which is the latest result from the task, will be nil if the task is not finished yet
error which is the latest error from the task, will be nil if the task did not emit an error yet
promiseQueue which is the promise DispatchQueue that run the task
isCompleted will be true if the task is complete or emitting an error
isError will be true the task emitting error
let promise = runPromise {
print("I'm running in the DispatchQueue.global(qos: .background)")
}.then {
print("I'm running on the same DispatchQueue as previous")
}
print(promise.isCompleted)
New Swift Async
Swift introduce new functionality which is async. Ergo can be used with new async too. To create a Promise from async method, use global asyncAwaitPromise:
asyncAwaitPromise {
await myAsyncFunction()
}.then { result in
print(result)
}
In case you want to treat Promise as async, just use result property from Promise:
let asyncResult = try await myPromise.result
It will return the result after finished and throwing error if error is hapens.
You can always convert Task to Promise too:
let promiseFromTask = myTask.asPromise()
or Promise to Task:
let taskFromPromise = myPromise.asTask()
Keep in mind that all of async functionality just available on macOS 10.15, iOS 13.0, watchOS 6.0 and tvOS 13.0
Creating Promise with asynchronous task
Sometimes the task you want to convert to Promise is already an asynchronous task. In this case, you can use asyncPromise instead of runPromise:
asyncPromise(on: .main) { consumer in
doSomethingAsync { result, error in
if let error = error {
consumer.reject(error)
} else if let result = result {
consumer.resolve(result)
}
}
}.then { result in
print(result)
}.handle { error in
print(error)
}
It will emit an error if done param is getting a nil result, or an error other than nil. If the result is not nil, it will run the next Promise task
The result of the asyncPromise is Promise, so you can always treat it as a regular Promise
Chain Animation (iOS only)
You can run animation using ChainAnimator which can be chain like Promise:
It will run animation from the first one and proceed to the next one after the last one is finished. You can chain as much animation as you need.
The result of animate is Promise of Bool. the Bool result will be true if all of the animation is succeed:
Ergo
Ergo is a framework for concurrent programming based on promise pipelining. It could help to avoid callback hell in complex asynchronous task
Example
To run the example project, clone the repo, and run
pod install
from the Example directory first.Requirements
Only Swift Package Manager
Installation
Cocoapods
Ergo is available through CocoaPods. To install it, simply add the following line to your Podfile:
Swift Package Manager from XCode
Swift Package Manager from Package.swift
Add as your target dependency in Package.swift
Use it in your target as
Ergo
Author
Nayanda Haberty, hainayanda@outlook.com
License
Impose is available under the MIT license. See the LICENSE file for more info.
Basic Usage
Ergo utilize the
Thenable
protocol which is implemented in thePromise
class that acts as a proxy for the concurrent task. To create a concurrent task in Promise, just call therunPromise
global method with any task you want to run:you can pass
DispatchQueue
to run on those queue:Chaining Promises
Promise
designed to be chainable with otherPromise
. To chain it, simply callthen
after eachPromise
:You can chain it as much as you need. All of the chaining
Promise
will be released from the chain after it’s finished doing its task.You can also pass a value from one
Promise
to another so it could be used there:Multiple Thenable
Promise
can be handled by multipleThenable
. All you need to do is just call as muchThenable
as you need after the particularPromise
:Handling Error
Promise
closure is throwable by default. You could always throw an error in thePromise
closure to stop nextThenable
to be executedYou can add error handler closure after then to catch the error and do something with it:
The error throws from
Promise
will always passed into all of its childPromise
Finally Block
Promise
have finally block which will always be executed regarding error or not the previousPromise
is. It will produce another promise which will called after finally is executed:Timeout
You can add a timeout to your promise that will automatically throw error if the task is not finished after the given timeout:
Droping a promise
Promise
can be dropped by calling thedrop
method. It will then emit an error and skip the current task if not finished yet. You can always pass custom errors when dropping so it will emit that error instead of the default one.Keep in mind that this will only drop the current
Promise
.finally
block andhandle
block will still be called:Continue with other Promise
You can continue then with new promise. Use
thenContinue
instead ofthen
, then return aPromise
:Combining Promises
You can combine up to 3
Promise
to be a singlePromise
ofTuple
as a Result:Since
waitPromises
actually just return back aPromise
ofTuple
, you can always treat it as regularPromise
Promise status
You can always check the
Promise
status using its object. its have some properties you can check:currentValue
which is the latest result from the task, will be nil if the task is not finished yeterror
which is the latest error from the task, will be nil if the task did not emit an error yetpromiseQueue
which is the promise DispatchQueue that run the taskisCompleted
will be true if the task is complete or emitting an errorisError
will be true the task emitting errorNew Swift Async
Swift introduce new functionality which is async. Ergo can be used with new async too. To create a
Promise
from async method, use globalasyncAwaitPromise
:In case you want to treat
Promise
as async, just useresult
property fromPromise
:It will return the result after finished and throwing error if error is hapens.
You can always convert
Task
toPromise
too:or
Promise
toTask
:Keep in mind that all of async functionality just available on macOS 10.15, iOS 13.0, watchOS 6.0 and tvOS 13.0
Creating Promise with asynchronous task
Sometimes the task you want to convert to Promise is already an asynchronous task. In this case, you can use
asyncPromise
instead ofrunPromise
:It will emit an error if
done
param is getting a nil result, or an error other than nil. If the result is not nil, it will run the nextPromise
task The result of theasyncPromise
isPromise
, so you can always treat it as a regularPromise
Chain Animation (iOS only)
You can run animation using
ChainAnimator
which can be chain likePromise
:It will run animation from the first one and proceed to the next one after the last one is finished. You can chain as much animation as you need. The result of animate is
Promise
ofBool
. theBool
result will be true if all of the animation is succeed:Since the result is regular
Promise
, you can always treat it as regularPromise
Contribute
You know how, just clone and do pull request