Like the title says I would like to make custom publisher that will basically function like deffered future. Normally when I want to encapsulate code in some Future, but want it to execute on subscription, I would need to write something like this:
Deffered {
Future { promise in
}
}
Now I was thinking of making custom publisher, something along the lines DefferedFuture that will have exact same functionality as Future, but will execute promise only on subscription?
The most obvious answer is this:
func single<Output, Failure>(_ promise: #escaping (#escaping (Result<Output, Failure>) -> Void) -> Void) -> Deferred<Future<Output, Failure>> where Failure: Error {
Deferred {
Future<Output, Failure>(promise)
}
}
If it absolutely must be a type rather than a function then:
extension Publishers {
struct Single<Output, Failure>: Publisher where Failure: Error {
let promise: (#escaping (Result<Output, Failure>) -> Void) -> Void
func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input {
Deferred { Future(promise) }
.subscribe(subscriber)
}
}
}
I'd like to provide Combine counterparts to completion closures which is becoming very cumbersome. Is there a shorter way or extension that can convert the following:
extension MyType {
func send(with request: URLRequest, completion: #escaping (Result<MyResponse, MyError>) -> Void) {
// Logic
}
func send(with request: URLRequest) -> Future<MyResponse, MyError> {
Future<MyResponse, MyError> { promise in
send(with: request) { result in
switch result {
case let .success(response):
promise(.success(response))
case let .failure(error):
promise(.failure(error))
}
}
}
}
}
The Future method is just a wrapper to the completion closure method. I was hoping to do at least something like this:
Future<MyResponse, MyError> { send(with:request, completion: $0) }
Is there a more elegant way to do this since this will be applied in a lot of places in my library.
Note that the completion parameter of the first send overload has the type:
Result<MyResponse, MyError>) -> Void
Which is exactly the same type as promise, which is
Future<MyResponse, MyError>.Promise
Promise is just a type alias for (Result<Output, Failure>) -> Void.
So you can just do:
Future<MyResponse, MyError> { promise in
send(with: request, completion: promise)
}
I'm having trouble understanding how to use a closure to handle completed events when passing in a function as a parameter.
Here's a very contrived example:
class MessageService {
func sendMessage(s: String) {
print(s)
}
var messenger: Messenger {
createMessenger(completion: sendMessage(s:))
}
}
func createMessenger(completion: #escaping (String) -> Void) -> Messenger {
return Messenger { completion("This is a hardcoded message.") }
}
struct Messenger {
let sendMessage: () -> Void
init(sendMessage: #escaping () -> Void) {
self.sendMessage = sendMessage
}
}
let service = MessageService()
let messenger = service.messenger
messenger.sendMessage()
I want to find out when sendMessage is finished (if for example it was performing something like a network request), so is there a way of having a completion handler for sendMessage so that I could write something along the lines of:
messenger.sendMessage {
print("I finished sending a message!")
}
I've tried adding a completion handler like this in the service class:
func sendMessage(s: String, completion: #escaping () -> Void) {
MessageRequest(with: s) {
completion()
}
}
But things started getting very confusing when I'm trying to use the createMessenger method, because the above function has some crazy type of (String, () -> ()) -> () which I don't know how to handle. Any help would be greatly appreciated, thanks.
So, it sounds like what you want is an arbitrary Messenger type, whose creator tell it what action to do, and once the action is done, it invokes its caller's completion handler.
It helps if you use typealias with descriptive names to keep track of all the closures. And if you don't mind, I'll name it more generically as Agent:
struct Agent {
typealias Completion = () -> Void
typealias Action = (Completion) -> Void
private let action: Action
static func create(action: #escaping Action) -> Agent {
Agent(action: action)
}
func execute(_ completion: #escaping Completion) {
action(completion)
}
}
So, Agent can be created with an arbitrary action that accepts a completion handler to signal when it's done:
let agent = Agent.create { completion in
print("started executing action")
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { completion() }
}
agent.execute { print("done") }
Now, you can adapt it to your MessengerService class:
class MessageService {
func sendMessage(s: String) {
print(s)
}
var messenger: Agent {
Agent.create { completion in
sendMessage("This is a hardcoded message.")
completion()
}
}
}
I have a bunch of functions with Result completion handlers that I’d like to convert to RxSwift.
They follow this convention:
func fetch(id: Int, completion: #escaping (Result<AuthorType, DataError>) -> Void) {...}
I could use the typical:
return Observable<AuthorType>.create { on(.next... }
Is there a more considerate generic way like PromiseKit does:
func fetch() -> Promise<AuthorType> {
return Promise { fetch(completion: $0.resolve) }
}
Anything like this possible in RxSwift?
There isn't a constructor like you are asking for out of the box, but it's easy enough to create:
extension ObservableType {
static func createFromResultCallback<E: Error>(_ fn: #escaping (#escaping (Result<Element, E>) -> Void) -> ()) -> Observable<Element> {
return Observable.create { observer in
fn { result in
switch result {
case .success(let value):
observer.onNext(value)
observer.onCompleted()
case .failure(let error):
observer.onError(error)
}
}
return Disposables.create()
}
}
}
For your example, it could be used like:
func fetch(id: Int) -> Observable<AuthorType> {
return .createFromResultCallback { fetch(id: id, $0) }
}
And if you have a function that only takes a callback like:
func shortFetch(_ completion: #escaping (Result<AuthorType, DataError>) -> Void)
Then you could create an Observable with the above by just doing:
Observable.createFromResultCallback(shortFetch)
Remember there is a major difference in behavior once you wrap the function in an Observable like this. Now it is cold which means it won't execute until after something subscribes to the observable and will execute every time something subscribes. This is unlike a Promise which will execute immediately and only once.
So i'm a bit lost on how to implement a retry logic when my upload request fail.
Here is my code i would like some guidance on how to do it
func startUploading(failure failure: (NSError) -> Void, success: () -> Void, progress: (Double) -> Void) {
DDLogDebug("JogUploader: Creating jog: \(self.jog)")
API.sharedInstance.createJog(self.jog,
failure: { error in
failure(error)
}, success: {_ in
success()
})
}
Here's a general solution that can be applied to any async function that has no parameters, excepting the callbacks. I simplified the logic by having only success and failure callbacks, a progress should not be that hard to add.
So, assuming that your function is like this:
func startUploading(success: #escaping () -> Void, failure: #escaping (Error) -> Void) {
DDLogDebug("JogUploader: Creating jog: \(self.jog)")
API.sharedInstance.createJog(self.jog,
failure: { error in
failure(error)
}, success: {_ in
success()
})
}
A matching retry function might look like this:
func retry(times: Int, task: #escaping(#escaping () -> Void, #escaping (Error) -> Void) -> Void, success: #escaping () -> Void, failure: #escaping (Error) -> Void) {
task(success,
{ error in
// do we have retries left? if yes, call retry again
// if not, report error
if times > 0 {
retry(times - 1, task: task, success: success, failure: failure)
} else {
failure(error)
}
})
}
and can be called like this:
retry(times: 3, task: startUploading,
success: {
print("Succeeded")
},
failure: { err in
print("Failed: \(err)")
})
The above will retry the startUploading call three times if it keeps failing, otherwise will stop at the first success.
Edit. Functions that do have other params can be simply embedded in a closure:
func updateUsername(username: String, success: #escaping () -> Void, failure: #escaping (Error) -> Void) {
...
}
retry(times: 3, { success, failure in updateUsername(newUsername, success, failure) },
success: {
print("Updated username")
},
failure: {
print("Failed with error: \($0)")
}
)
Update So many #escaping clauses in the retry function declaration might decrease its readability, and increase the cognitive load when it comes to consuming the function. To improve this, we can write a simple generic struct that has the same functionality:
struct Retrier<T> {
let times: UInt
let task: (#escaping (T) -> Void, #escaping (Error) -> Void) -> Void
func callAsFunction(success: #escaping (T) -> Void, failure: #escaping (Error) -> Void) {
let failureWrapper: (Error) -> Void = { error in
// do we have retries left? if yes, call retry again
// if not, report error
if times > 0 {
Retrier(times: times - 1, task: task)(success: success, failure: failure)
} else {
failure(error)
}
}
task(success, failureWrapper)
}
func callAsFunction(success: #escaping () -> Void, failure: #escaping (Error) -> Void) where T == Void {
callAsFunction(success: { _ in }, failure: failure)
}
}
Being callable, the struct can be called like a regular function:
Retrier(times: 3, task: startUploading)(success: { print("success: \($0)") },
failure: { print("failure: \($0)") })
, or can be circulated through the app:
let retrier = Retrier(times: 3, task: startUploading)
// ...
// sometime later
retrier(success: { print("success: \($0)") },
failure: { print("failure: \($0)") })
Here is an updated answer for swift 3. I also added a generic object in the success block so if you make an object after your network call is complete you can pass it along to the final closure. Here is the retry function:
func retry<T>(_ attempts: Int, task: #escaping (_ success: #escaping (T) -> Void, _ failure: #escaping (Error) -> Void) -> Void, success: #escaping (T) -> Void, failure: #escaping (Error) -> Void) {
task({ (obj) in
success(obj)
}) { (error) in
print("Error retry left \(attempts)")
if attempts > 1 {
self.retry(attempts - 1, task: task, success: success, failure: failure)
} else {
failure(error)
}
}
}
And here is how you would use it if you updated a user and wanted to get back a new user object with the updated info:
NetworkManager.shared.retry(3, task: { updatedUser, failure in
NetworkManager.shared.updateUser(user, success: updatedUser, error: failure) }
, success: { (updatedUser) in
print(updatedUser.debugDescription)
}) { (err) in
print(err)
}
Updated to swift 5, with Result type instead of success and failure blocks.
func retry<T>(_ attempts: Int, task: #escaping (_ completion:#escaping (Result<T, Error>) -> Void) -> Void, completion:#escaping (Result<T, Error>) -> Void) {
task({ result in
switch result {
case .success(_):
completion(result)
case .failure(let error):
print("retries left \(attempts) and error = \(error)")
if attempts > 1 {
self.retry(attempts - 1, task: task, completion: completion)
} else {
completion(result)
}
}
})
}
This is how we can use the retry function:
func updateUser(userName: String) {
retry(3, task: { (result) in
startUploadingWithResult(userName: userName, completion: result)
}) { (newResult) in
switch newResult {
case .success(let str):
print("Success : \(str)")
case .failure(let error):
print(error)
}
}
}
updateUser(userName: "USER_NAME")