I want to chain following operations
createUserandVerify
Create Anonymous User (user)
Verify User -> verifiedUser
If verification successful return verifiedUser else return user
Get stuff with coredata getStuff
If stuff.count > 0 Upload stuff with user credentials uploadStuff
Finally report the result of all operations
I wrote createUserandVerify as below. I wonder how should I write uploadStuff in reactive way. Upload function depends on user credentials. Therefore It must only run after createUserandVerify. I know I could just check count of array inside uploadStuff and return empty but I wonder the best practices.
func createUserandVerify() -> Single<User> {
return Service.sharedInstance.generateAnonUser()
.flatMap{ user in
if Service.sharedInstance.isOldRegisteredUser {
print("It is old user")
// We need to verify the receipt
return Service.sharedInstance.verifyReceipt()
.flatMap { verifiedUser in
print("Returning Verified new user [Verification Success]")
return Single.just((verifiedUser))
}.catchError{ error ->Single<User> in
print("Returning firstly created user [Verification Failed]")
print("Error Type: \(error)")
return Single.just(user)
}
} else {
//Normal anonymous old user
print("Returning firstly created user [Anonymous]")
return Single.just(user)
}
}
}
Assumptions (since I have not worked with Single I changed them to Observable):
func createUserandVerify() -> Observable<User>
func getStuff() -> [Stuff]
func uploadStuff(_ user: User) -> Observable<String>
createUserandVerify() should publish errors with onError so uploadStuff will not be called if something goes wrong.
Possible solution:
enum CustomError: Error {
case instanceMissing
case notEnoughStuff
}
createUserandVerify()
.flatMap { [weak self] (user) -> Observable<String> in
guard let strongSelf = self else { throw CustomError.instanceMissing }
guard strongSelf.getStuff().count > 0 else { throw CustomError.notEnoughStuff }
return strongSelf.uploadStuff(user)
}
.subscribe(
onNext: { stringResult in
// print result from 'uploadStuff'
print(stringResult)
},
onError: { error in
// will be reached if something goes
// wrong in 'createUserandVerify' or 'uploadStuff'
// or if one of your custom errors in 'flatMap' are thrown
print(error)
})
.disposed(by: disposeBag)
You could also make getStuff reactive by returning an Observable or Single and also include it in the chain via flatMap.
Related
I have a logic problem. The premise of what I would like to achieve is to combine two sources of data and in case of failure, I would like to run retry only on one observable. The logic flow goes like this: I try and fail to get local data from StorageManager class, afterwards I try to get the data from an API. In case that API call fails, I would like to retry only that API call a certain number of times. Is there a nice way of doing this? By just running retry like in the code below, the local data observable gets triggered.
class func loginUser(user: User, replaySubject: ReplaySubject<User>){
let savedUserObs = StorageManager.readCachedModelData(requestType: .LOGIN, modelType: User.self)
let apiUserObs = NetworkHelper.makeRequest(requestType: .LOGIN).map { response -> User in
guard let user = response.user?.first else { throw TempErrors.wasNotAbleToExtractUser }
return user
}
savedUserObs.concat(apiUserObs)
.retry { errorObs in
errorObs.scan(0) { attempt, error in
let max = 5
if attempt == max { throw TempErrors.tooManyRetries }
return attempt + 1
}
}
.subscribe { user in
replaySubject.onNext(user)
StorageManager.saveData(requestType: .LOGIN, data: user)
} onError: { error in
print("onerror error: \(error)")
} onCompleted: {
print("completed")
} onDisposed: {
print("disposed")
}.disposed(by: disposeBag)
}
I deleted an entry in the Firestore and also checked it manually to confirm that. However, as long as I do not close the application, I can send a request to fetch the data and I still get the result. This should not be the case.
If you imagine having a shared photo with some textual information and you delete those information, this would mean, other users can still see the textual information (fetched from the Firestore) but not the image anymore (store in Firestorage).
I want to display a message on the UI, something like "The content does not exist anymore".
How I can achieve that? I used the following approach so far but it does not work at the moment:
public func checkIfChallengeObjectExists(completionHandler:#escaping(Bool)->(), challengeId:String) {
CHALLENGE_COLLECTION?.document(challengeId).getDocument(completion: { (querySnapshot, error) in
if (error != nil) {
print(error?.localizedDescription as Any)
}
if (querySnapshot?.documentID == "" || querySnapshot!.metadata.isFromCache) {
completionHandler(false)
}
else {
completionHandler(true)
}
})
}
Any solutions?
Non-existent documents will still return document snapshots, but they will be empty. Therefore, you must check the contents of the snapshot for the document, not the snapshot itself. Also, you should handle errors and the overall flow of the return better.
public func checkIfChallengeObjectExists(completionHandler:#escaping(Bool)->(), challengeId:String) {
CHALLENGE_COLLECTION?.document(challengeId).getDocument(completion: { (querySnapshot, error) in
if let doc = querySnapshot,
doc.exists {
completionHandler(true) // only one possible true condition
} else {
if let error = error {
print(error.localizedDescription)
}
completionHandler(false) // all else false
}
})
}
As a side note, I recommend reordering the parameters of the function to make it easier to read when called (conventionally, the completion handler comes last) and giving the boolean argument a name so it's easier to read when referencing (sometime later or by other developers).
public func verifyChallengeObject(ID: String, _ completion: #escaping (_ exists: Bool) -> Void) {
...
}
verifyChallengeObject(ID: "abc123", { (exists) in
if exists {
...
} else {
...
}
})
I have the following pipeline setup, and for some reason I can't understand, the second flatMap is skipped:
func letsDoThis() -> SignalProducer<(), MyError> {
let logError: (MyError) -> Void = { error in
print("Error: \(error); \((error as NSError).userInfo)")
}
return upload(uploads) // returns: SignalProducer<Signal<(), MyError>.Event, Never>
.collect() // SignalProducer<[Signal<(), MyError>.Event], Never>
.flatMap(.merge, { [uploadContext] values -> SignalProducer<[Signal<(), MyError>.Event], MyError> in
return context.saveSignal() // SignalProducer<(), NSError>
.map { values } // SignalProducer<[Signal<(), MyError>.Event], NSError>
.mapError { MyError.saveFailed(error: $0) } // SignalProducer<[Signal<(), MyError>.Event], MyError>
})
.flatMap(.merge, { values -> SignalProducer<(), MyError> in
if let error = values.first(where: { $0.error != nil })?.error {
return SignalProducer(error: error)
} else {
return SignalProducer(value: ())
}
})
.on(failed: logError)
}
See the transformations/signatures starting with the upload method.
When I say skipped I mean even if I add breakpoints or log statements, they are not executed.
Any idea how to debug this or how to fix?
Thanks.
EDIT: it is most likely has something to do with the map withing the first flatMap, but not sure how to fix it yet.
See this link.
EDIT 2: versions
- ReactiveCocoa (10.1.0):
- ReactiveObjC (3.1.1)
- ReactiveObjCBridge (6.0.0):
- ReactiveSwift (6.1.0)
EDIT 3: I found the problem which was due to my method saveSignal sending sendCompleted.
extension NSManagedObjectContext {
func saveSignal() -> SignalProducer<(), NSError> {
return SignalProducer { observer, disposable in
self.perform {
do {
try self.save()
observer.sendCompleted()
}
catch {
observer.send(error: error as NSError)
}
}
}
}
Sending completed make sense, so I can't change that. Any way to change the flatMap to still do what I intended to do?
I think the reason your second flatMap is never executed is that saveSignal never sends a value; it just finishes with a completed event or an error event. That means map will never be called, and no values will ever be passed to your second flatMap. You can fix it by doing something like this:
context.saveSignal()
.mapError { MyError.saveFailed(error: $0) }
.then(SignalProducer(value: values))
Instead of using map (which does nothing because there are no values to map), you just create a new producer that sends the values after saveSignal completes successfully.
I'm trying to write a function using Swift and Vapor but I don't understand why one statement gets printed before the other:
// Logout user
func logout(_ req: Request) throws -> Future<APIResponseMessage> {
let userID = self.checkAccessToken(req: req)
// Delete access token here
let apiResponseMessage = APIResponseMessage()
apiResponseMessage.message = "success"
apiResponseMessage.userID = userID
return apiResponseMessage.create(on: req)
}
func checkAccessToken(req: Request) -> Int {
let bearerAuthorization = req.http.headers.bearerAuthorization
guard let _bearerAuthorization = bearerAuthorization else {
// Works fine
print("no bearer incluced")
return 0
}
let _ = AccessToken.query(on: req).filter(\.accessToken == _bearerAuthorization.token).first().map(to: Int.self) { queriedAccessToken in
// This should be first
print("This gets printed second")
return queriedAccessToken!.userID!
}
// This should be second
print("This gets printed first")
return 0
}
Can anyone tell me how to make the second print statement wait until the first one is completed?
Right now it's causing my logout function to run with userID == 0 when this shouldn't be the case
As #nathan said, this is due to your code being async. Your .map callback is like the closure you pass into to a URLSession.dataTask when making request's to an external API for an iOS app.
Vapor uses a slightly different async model then what you use in iOS though, using promises and futures instead of callback closures. You can read about them in the docs.
In your case, you want to return the userID you get from the AccessToken query. To do this, you first need to change your method's return type from Int to Future<Int>. Then, instead of assigning the result of the .map call to _, you can return it from the method:
func checkAccessToken(req: Request) -> Future<Int> {
let bearerAuthorization = req.http.headers.bearerAuthorization
guard let _bearerAuthorization = bearerAuthorization else {
return req.future(0)
}
return AccessToken.query(on: req).filter(\.accessToken == _bearerAuthorization.token).first().map(to: Int.self) { queriedAccessToken in
return queriedAccessToken!.userID!
}
}
I would suggest you look into error handling for your queriedAccessToken and userID values so you aren't force-unwrapping them.
I have a recursive, async function that queries Google Drive for a file ID using the REST api and a completion handler:
func queryForFileId(query: GTLRDriveQuery_FilesList,
handler: #escaping FileIdCompletionHandler) {
service.executeQuery(query) { ticket, data, error in
if let error = error {
handler(nil, error)
} else {
let list = data as! GTLRDrive_FileList
if let pageToken = list.nextPageToken {
query.pageToken = pageToken
self.queryForFileId(query: query, handler: handler)
} else if let id = list.files?.first?.identifier {
handler(id, nil)
} else {
handler(nil, nil) // no file found
}
}
}
}
Here, query is set up to return the nextPageToken and files(id) fields, service is an instance of GTLRDriveService, and FileIdCompletionHandler is just a typealias:
typealias FileIdCompletionHandler = (String?, Error?) -> Void
I've read how to convert async functions into promises (as in this thread) but I don't see how that can be applied to a recursive, async function. I guess I can just wrap the entire method as a Promise:
private func fileIdPromise(query: GTLRDriveQuery_FilesList) -> Promise<String?> {
return Promise { fulfill, reject in
queryForFileId(query: query) { id, error in
if let error = error {
reject(error)
} else {
fulfill(id)
}
}
}
}
However, I was hoping to something a little more direct:
private func queryForFileId2(query: GTLRDriveQuery_FilesList) -> Promise<String?> {
return Promise { fulfill, reject in
service.executeQuery(query) { ticket, data, error in
if let error = error {
reject(error)
} else {
let list = data as! GTLRDrive_FileList
if let pageToken = list.nextPageToken {
query.pageToken = pageToken
// WHAT DO I DO HERE?
} else if let id = list.files?.first?.identifier {
fulfill(id)
} else {
fulfill(nil) // no file found
}
}
}
}
}
So: what would I do when I need to make another async call to executeQuery?
If you want to satisfy a recursive set of promises, at where your "WHAT DO I DO HERE?" line, you'd create a new promise.then {...}.else {...} pattern, calling fulfill in the then clause and reject in the else clause. Obviously, if no recursive call was needed, though, you'd just fulfill directly.
I don't know the Google API and you didn't share your code for satisfying a promise for a list of files, so I'll have to keep this answer a bit generic: Let's assume you had some retrieveTokens routine that returned a promise that is satisfied only when all of the promises for the all files was done. Let's imagine that the top level call was something like:
retrieveTokens(for: files).then { tokens in
print(tokens)
}.catch { error in
print(error)
}
You'd then have a retrieveTokens that returns a promise that is satisfied only when then promises for the individual files were satisfied. If you were dealing with a simple array of File objects, you might do something like:
func retrieveTokens(for files: [File]) -> Promise<[Any]> {
var fileGenerator = files.makeIterator()
let generator = AnyIterator<Promise<Any>> {
guard let file = fileGenerator.next() else { return nil }
return self.retrieveToken(for: file)
}
return when(fulfilled: generator, concurrently: 1)
}
(I know this isn't what yours looks like, but I need this framework to show my answer to your question below. But it’s useful to encapsulate this “return all promises at a given level” in a single function, as it allows you to keep the recursive code somewhat elegant, without repeating code.)
Then the routine that returns a promise for an individual file would see if a recursive set of promises needed to be returned, and put its fulfill inside the then clause of that new recursively created promise:
func retrieveToken(for file: File) -> Promise<Any> {
return Promise<Any> { fulfill, reject in
service.determineToken(for: file) { token, error in
// if any error, reject
guard let token = token, error == nil else {
reject(error ?? FileError.someError)
return
}
// if I don't have to make recursive call, `fulfill` immediately.
// in my example, I'm going to see if there are subfiles, and if not, `fulfill` immediately.
guard let subfiles = file.subfiles else {
fulfill(token)
return
}
// if I got here, there are subfiles and I'm going to start recursive set of promises
self.retrieveTokens(for: subfiles).then { tokens in
fulfill(tokens)
}.catch { error in
reject(error)
}
}
}
}
Again, I know that the above isn't a direct answer to your question (as I'm not familiar with Google Drive API nor how you did your top level promise logic). So, in my example, I created model objects sufficient for the purposes of the demonstration.
But hopefully it's enough to illustrate the idea behind a recursive set of promises.