I have this code, i want do a request a url
if(!m.sendRequest()){
appDelegate.setValues()
}
this line is executed first
appDelegate.setValues()
i want wait this condition
!m.sendRequest()
structure sendRequest with a completion
func sendRequest(com:#escaping((Bool) -> ())) {
Api.sendReq {
com(true)
}
}
sendRequest { value in
if value {
appDelegate.setValues()
}
}
Related
I have a async function func doWork(id: String) async throws -> String. I want to call this function from a concurrent dispatch queue like this to test some things.
for i in 1...100 {
queue.async {
obj.doWork(id: "foo") { result, error in
...
}
}
}
I want to do this because queue.async { try await obj.doWork() } is not supported. I get an error:
Cannot pass function of type '#Sendable () async throws -> Void' to parameter expecting synchronous function type
But the compiler does not provide me with a completion handler version of doWork(id:). When I call it from Obj C, I am able to use the completion handler version: [obj doWorkWithId: #"foo" completionHandler:^(NSString * val, NSError * _Nullable error) { ... }]
How do I do something similar in Swift?
You can define a DispatchQueue. This DispatchQueue will wait for the previous task to complete before proceeding to the next.
let queue = DispatchQueue(label: "queue")
func doWork(id: String) async -> String {
print("Do id \(id)")
return id
}
func doWorksConcurrently() {
for i in 0...100 {
queue.async {
Task.init {
await doWork(id: String(i))
}
}
}
}
doWorksConcurrently()
You are initiating an asynchronous task and immediately finishing the dispatch without waiting for doWork to finish. Thus the dispatch queue is redundant. One could do:
for i in 1...100 {
Task {
let results = try await obj.doWork(id: "foo")
...
}
}
Or, if you wanted to catch/display the errors:
for i in 1...100 {
Task {
do {
let results = try await obj.doWork(id: "foo")
...
} catch {
print(error)
throw error
}
}
}
Now, generally in Swift, we would want to remain within structured concurrency and use a task group. But if you are trying to mirror what you'll experience from Objective-C, the above should be sufficient.
Needless to say, if your Objective-C code is creating a queue solely for the purpose for calling the completion-handler rendition of doWork, then the queue is unnecessary there, too. But we cannot comment on that code without seeing it.
I have array of Observables, say [Observable <WriteTaskResult>]
I want to perform all write tasks keeping their order, and if any one of them fails then I want to perform Observable<ResetTaskResult>
Following function will return observable of type BatchTasksResult for tracking tasks progress.
Sample Code:
enum BatchTasksResult{
case elapsedTime(Double)
case failedFatal
case rolledback
case success
}
func writeBlocks(tasks: [WriteTask]) -> Observable<BatchTasksResult>{
return Observable.create {(observable) -> Disposable in
let allTasks: [Observable<WriteTaskResult>] = self.writeSomewhere(tasks)
Observable.concat(allTasks)
.subscribe { writeTaskResult in
observable.onNext(.elapsedTime(writeTaskResult.totalTime))
}
onError: { (err) in
// Perform Observable<ResetTaskResult>
// if ResetTask was successful then observable.onNext(.rolledback)
// if ResetTask failed then observable.onNext(.failedFatal)
}
onCompleted: {
observable.onNext(.success)
}
.disposed(by: disposeBag)
return Disposables.create()
}
}
How do I trigger rollback logic using Observable from onError of allTasks' observable?
Simple solution seems nested observable, but that's not good practice, I guess? I tried FlatMap, but it can't really solve "If any sinlge task fails, then rollback and reset" Any other solution to this?
There's no need to add the extra level of indirection with the create function. Every Observable operator already creates a new object.
And when you do use Observable.create, do not dispose in an external dispose bag and return a Disposables.create(). Just return the disposable that you just created.
Here's the appropriate way to do what you want:
func writeBlocks(tasks: [WriteTask], resetTask: Single<ResetTaskResult>) -> Observable<BatchTasksResult> {
// create the array of write tasks and concat them. You seem to have that down.
let result = Observable.concat(tasks.map(writeSomewhere(task:)).map { $0.asObservable() })
.share() // the share is needed because you are using the value twice below.
return Observable.merge(
// push out the elapsed time for each task.
result.map { BatchTasksResult.elapsedTime($0.totalTime) },
// when the last one is done, push out the success event.
result.takeLast(1).map { _ in BatchTasksResult.success }
)
.catch { _ in
resetTask // the resetTask will get subscribed to if needed.
.map { _ in BatchTasksResult.rolledback } // if successful emit a rollback
.catch { _ in Single.just(BatchTasksResult.failedFatal) } // otherwise emit the failure.
.asObservable()
}
}
func writeSomewhere(task: WriteTask) -> Single<WriteTaskResult> {
// create a Single that performs the write and emits a result.
}
I have a function called chaining which chains multiple promises and I want to call that function multiple times. For that, I am using a for loop and I want that with index 0, the chaining function should be executed with index 0. (I have an array of properties ListingArray[] and I want to use ListingArray[0] in one iteration of loop, ListingArray[1] in other iteration and so on).
Here is my chaining function:
func chaining() {
firstly {
Promise_getIDOfOwnerFromCollection()
}.then { (IdsInCollection)-> Promise<[String]> in
return self.Promise_getOwnerListings(IDs: IdsInCollection)
}.then { (ownerListings) ->Promise<Void> in
return self.Promise_getReviews(ListingIDs: ownerListings)
}.done { (arg0) in
let () = arg0
print("Work Done")
}.catch { (error) in
print("Error is \(error.localizedDescription)")
}
}
And I am calling that function in loop like this.
for Count in 0...4 {
chaining()
}
Now the problem is that the function inside firstly is instantly called 5 times before then is executed. And I want the sequence to be like with Count 0, chaining function should execute one time and then with Count 1, function should execute again.
The behaviour happening in your code is completely expected. Given you're instantiating the chaining 4 times, therefore the firstly job will be executed such number of times.
Instead you will need somehow provide a single instance of work for the firstly.
Currently: execute N times { firstly + rest of the job }
Expected: firstly + execute N times { rest of the job }
Here a code example based on yours.
struct Review {
}
func Promise_getReviews(listingIDs ids: [String]) -> Promise<[Review]> {
}
func Promise_getOwnerListings(IDs ids: [String]) -> Promise<[String]> {
}
func chaining(from initialWork: Promise<[String]>) {
firstly { when(fulfilled: initialWork) }
.then(Promise_getOwnerListings)
.then(Promise_getReviews)
.done { print("Work Done") }
.catch { print("Error") }
}
let initialWork = Promise<[String]>(["1","2", "3"])
(0...3).forEach { chaining(from: initialWork) }
I have a series of UIViewControllers (popups) in my app that I use to gather information from the end user. I managed to chain them with promises like this:
firstly {
return showFirstPopup()
}
.then { info1 -> Promise<Info2> in
dismissFirstPopup()
//Do sth with info1...
return showSecondPopup()
}
.then { info2 -> Promise<Info3> in
dismissSecondPopup()
//Do sth with info2...
return showThirdPopup()
}
.then { info3 -> Promise<Info4> in
dismissThirdPopup()
//Do sth with info3...
return showForthPopup()
}
...
.catch { error in
//Handle any error or cancellation...
}
If, for example, the user presses back in the third popup I need to go back to the previous "then" and not cancel the whole flow.
Basically I need to be able to go back one step, let the user edit the data, then continue the flow.
Is there a way to do this with PromiseKit?
This is what I ended up using. Hope this helps someone else out there as well.
func myFunc(info: Info) -> Promise<()>
{
return Promise { fulfill, reject in
let firstPromise = shouldShowFirstPopup ? showFirstPopup() : Promise(value: info.info1)
firstPromise
.then { info1 -> Promise<Info2> in
info.info1 = info1
if (info.shouldShowSecondPopup) {
return showSecondPopup()
}
return Promise(value: info.info2)
}
.then { info2 -> Promise<Info3> in
info.info2 = info2
info.shouldShowSecondPopup = false
if (info.shouldShowThirdPopup) {
return showThirdPopup()
}
return Promise(value: info.info3)
}
.then { info3 -> Promise<()> in
info.info3 = info3
info.shouldShowThirdPopup = false
return processEverything()
}
.then { _ -> () in
fulfill(())
}
.catch { error in
switch error {
case MyErrors.backPressed(let popupType):
switch popupType {
case .firstPopup:
reject(MyErrors.userCanceled)
return
case .secondPopup:
info.shouldShowFirstPopup = true
info.shouldShowSecondPopup = true
case .thirdPopup:
info.shouldShowSecondPopup = true
info.shouldShowThirdPopup = true
default:
reject(MyErrors.defaultError(message: "Not implemented case exception"))
return
}
firstly {
return myFunc(info: info)
}
.then { _ -> () in
fulfill(())
}
.catch { error2 in
reject(error2)
}
default:
reject(error)
}
}
}
}
Here is what I found about PromiseKit :
PromiseKit has the concept of cancellation baked in. If the user cancels something then typically you don’t want to continue a promise chain, but you don’t want to show an error message either. So what is cancellation? It’s not success and it’s not failure, but it should handle more like an error, ie. it should skip all the subsequent then handlers. PromiseKit embodies cancellation as a special kind of error.
According to this you are going to skip all the subsequent then handlers.
Here is the link where I found this information : Promise Kit
I'm trying to generate an array of PFObjects called 'areaList'. I've been researching this quite a bit and understand that I could benefit from using a completion handler to handle the asynchronous nature of the loaded results. My ask, specifically, is to get some guidance on what I'm doing wrong as well as potential tips on how to achieve the result "better".
Here is my query function with completion handler:
func loadAreasNew(completion: (result: Bool) -> ()) -> [Area] {
var areaList = self.areaList
let areaQuery = PFQuery(className: "Area")
areaQuery.findObjectsInBackgroundWithBlock {
(areas: [PFObject]?, error: NSError?) -> Void in
if error == nil {
for area in areas! {
let areaToAdd = area as! Area
areaList.append(areaToAdd)
// print(areaList) // this prints the list each time
// print(areaToAdd) // this prints the converted Area in the iteration
// print(area) // this prints the PFObject in the iteration
if areaList.count == areas!.count {
completion(result: true)
} else {
completion(result: false)
}
}
} else {
print("There was an error")
}
}
return areaList
}
Here is how I'm attempting to call it in viewDidLoad:
loadAreasNew { (result) -> () in
if (result == true) {
print(self.areaList)
} else {
print("Didn't Work")
}
}
I assigned this variable before viewDidLoad:
var areaList = [Area]()
In the console, I get the following:
Didn't Work
Didn't Work
Didn't Work
Didn't Work
[]
Representing the 5 items that I know are there in Parse...
This is an interesting question. First off, PFQuery basically has a built in completion handler, which is quiet nice! As you probably know, all of the code within the areaQuery.findObjectsInBackgroundWithBlock {...} triggers AFTER the server response. A completion most often serves the purpose of creating a block, with the ability of asynchronously returning data and errors.
Best practice would (IMO) to just call the code that you want to use with the results from your PFQuery right after your area appending loop (which I'm gonna take out because I'm picky like that), like so:
func loadAreasNew() {
var areaList = self.areaList
let areaQuery = PFQuery(className: "Area")
areaQuery.findObjectsInBackgroundWithBlock {
(areas: [PFObject]?, error: NSError?) -> Void in
if error == nil {
let areasFormatted = areas! As [Areas]
areasList += areasFormatted
//Something like this
self.codeINeedAreasFor(areasList)
}
} else {
print(error)
}
}
}
HOWEVER! If you really feel the need to use some completion handlers, check out this other answer for more info on how to use them. But keep in mind all tools have a time and a place...
There are a few issues here.
Your completion handler doesn't require you to define the name for your completion handler's parameters, so you could easily use completion: (Bool) -> ()
Further in your function, you're returning areaList. This should be put through the completion handler like this onComplete(areaList) and change your completion handler parameter to expect your area list.
Then, when you call your function, it could look more like this :
loadAreasNew { result in
if (result == true) {
print(self.areaList)
} else {
print("Didn't Work")
}
}
Here is my concern:
1) Don't pass in a local variable and make the function return it, it's meaningless and danger.
You may want to initiate an empty array and make your fetch, then "return" it.
2) The fetch request is processed in background, you will have no idea when it will have finished. If you return the array immediately it will always be an empty array.
Put the "return" in your completion too.
3) Parse already has a distance checking method, you don't have to do it manually. aPARSEQUERRY.where(key:,nearGeoPoint:,inKilometers:)
I will rewrite the function as:
func loadNewAreas(completion:([Area],err?)->()){
let areaQuery = PFQuery(className: "Area")
areaQuery.where("location",nearGeoPoint:MYCURRENTLOCATION,inKilometers:50)
areaQuery.findObjectInBackgroundWithBlock(){objects,err
if objects.count == 0
{
completion([],err)
}
let areas = Area.areasFromPFObjects(objects)
completion(areas,err)
}
}