I'm trying to chain a number of promises that need to be resolved before returning.
In my case, for each element of databaseResult I need to fetch some data with a method that returns a promise.
Once I've fetched the data for every single element of the array I need to return to the calling method.
var toReturn = [MatchModel]()
//get my array of data
let databaseResults = MatchDatabaseManager.getMatchList();
//not sure what I'm doing
var promise = dispatch_promise{ 0 }
if(databaseResults.count > 0) {
return Promise { fulfill, reject in
for index in 0..<databaseResults.count {
print(index)
promise = promise.then { y -> Promise<Int> in
//Fetch the data I need ...
DataProvider.getUserProfileWithUserId(
(databaseResults[y].partnerUserProfile?.userId)!)
.then {(model) in {
//and use it to create the data I need to return
toReturn.append(MatchModel(realmModel:
databaseResults[y], partnerProfile: model))
}
}
return dispatch_promise { index }
}
}
//Once all the promises are fulfilled, toReturn contains the data I need and I can return it
promise.then{ x in {
fulfill(toReturn)
}
}
}
}
If I run this I get
PromiseKit: Pending Promise deallocated! This is usually a bug
I have very little experience with PromiseKit and documentation / exaples are scarce, so I have no idea what I'm missing here.
After asking the library developer for some help, I found out one must use "when" to wait for a series of promises to be completed.
The solution to the problem then becomes
return when(databaseResults.map{ (dbresult : MatchRealmModel) in
return DataProvider.getUserProfileWithUserId((dbresult.partnerUserProfile?.userId)!).then { model in
return MatchModel(realmModel: dbresult, partnerProfile: model)
}
})
I also found that a when() call with an empty array as the parameter can cause this issue.
Related
Between these 2 codes
( returns -> Void)
static func dropboxWorkoutList ( userCompletionHandler: #escaping ([String]) -> Void) {
let client = DropboxClientsManager.authorizedClient
var files: [String] = []
_ = client?.files.listFolder(path: "/workouts")
.response { response, error in
if let result = response {
for entry in result.entries {
if let file = entry as? Files.FileMetadata {
let ext = (file.name as NSString).pathExtension
switch ext {
case "txt", "mrc", "zwo":
// print("filename:\(file.name) ext:\(ext) path:\(file.pathLower)")
files.append(file.pathLower!)
default:
continue
}
}
}
files = files.sorted(by: { $0 < $1 } )
// print("Contents of Workout Folder:\(files)")
userCompletionHandler(files)
} else if let error = error {
print(error)
}
}
}
calling it
dropboxFunc.dropboxWorkoutList() { (value) in
print("value:\(value)")
print("value[0] : \(value[0])")
print("value.count : \(value.count)")
}
results in:
value:["/1-01.txt", "/1-17.txt"]
value[0] : /1-01.txt
value.count : 5
however when changing it
from Return -> Void
to Return -> Any
trying to execute the below will have swift telling me:
"Missing return in a closure expected to return 'Any'"
dropboxFunc.dropboxWorkoutList() { (value) in
print("value:\(value)")
print("value[0] : \(value[0])")
print("value.count : \(value.count)")
}
I can only allowed to execute 1 print statement. Just want to understand the difference.
Note: Asked this
Return list of files from function call
and was given this as possible answer
How I can return value from async block in swift
Specifying a closure of ([String]) -> Any means that the closure is going to return something, and it is of type Any. But in your examples, (a) your closures are not returning anything at all; and (b) the dropboxWorkoutList does not appear to need/use an object returned by the closure supplied by the caller, anyway. This is a “completion handler closure” pattern, and completion handler closures almost always have a Void return type.
I actually want to use the return values from dropboxWorkoutList to populate a tableview which I've not coded yet
OK, then I think you may be conflating the closure parameter (what dropboxWorkoutList will be supplying back to the caller) and the closure’s return value (the far less common scenario, where the caller needs to supply dropboxWorkoutList some value based upon the closure’s parameter). In this case, you want the former (the closure parameter), not the latter (the closure’s return value).
You likely do not want to change the closure to be ([String]) -> Any, at all, but rather leave it as ([String]) -> Void. The caller should just take the parameter of the closure and use that. For example:
dropboxFunc.dropboxWorkoutList { values in
self.strings = values // update whatever model object your table view data source is using
self.tableview.reloadData() // and tell the table view to reload the data
}
In short, your question here (and in that other post) was, effectively, “how do I return a value using a closure?”, to which the answer is that you should not think of it as “returning” a value. Rather, in asynchronous routines with completion handler closures, results are supplied as parameter(s) to the closure. Essentially, dropboxWorkoutList is calling a function (in this case, a closure) to say “hey, here is your data”. It is providing data to that closure, not returning data from that closure.
In my ViewModel file I have an observable array created after applying map on it. Now before returning it I want to check if it has any content or not. If there is nothing in there I want to return it without applying map. Following is my code:
func retrieveDeals(location: CLLocation?) -> Observable<[SaleItem]> {
let specials = nearestFlightSpecials.retrieveNearestFlightSpecials(userLocation: location)
let happyHourDeals = specials.map {
$0.filter { $0.isHappyHour }
}
return happyHourDeals
}
Before I return happyHourDeals I want to check if it contains any element or not. The above array is subscribed in view but I don't want to apply the above logic there. I want to keep it here in ViewModel.
I suspect what you want to do is filter out empty output:
func retrieveDeals(location: CLLocation?) -> Observable<[SaleItem]> {
let specials = nearestFlightSpecials.retrieveNearestFlightSpecials(userLocation: location)
let happyHourDeals = specials.map {
$0.filter { $0.isHappyHour }
}
.filter { !$0.isEmpty } // this is the line you need.
return happyHourDeals
}
Terminology is important here. Observables don't "contain" values. Observables don't return values, they emit events.
Your happyHourDeals will still be returned but with the filter line, it will no longer emit empty arrays. What this means is that whatever is subscribed to the value returned will not be updated if specials.map { $0.filter { $0.isHappyHour } } emits an empty array.
I have an Observable and would like it to filter elements that exists in an external array. The problem is that an element obviously can't be compared directly to an array, so I loop the array and then make the comparison. This doesn't work because the return statement needs to be outside of the for loop.
func scanAndFilterCoreData() -> Observable<BleHandler.BlePeripheral> {
let request = NSFetchRequest<LocalDoorCoreDataObject>(entityName: "LocalDoorCoreDataObject")
let result = self.coreDataHandler.fetchAll(fetchRequest: request)
return bleHandler.scan(serviceId: AppSettings.discoverServiceId)
.flatMap{ Observable.from($0) }
.filter { value in
for coreData in result {
return value.peripheral.identifier.uuidString == coreData.dPeripheralId
}
}
}
Being new to Rx I'm thinking there has to be some way to include the external array into the Observable as a second parameter, or how is this done?
You should use map instead of filter:
.map { value in
let uuidString = value.peripheral.identifier.uuidString
return result.filter { $0.dPeripheralId == uuidString }
}
I would like to learn about promises in swift-4. How to use multiple then statements and done, catch blocks.
Here I am trying to get the value from the promise. But I'm getting errors. Could someone help me to understand promises?
Here is my code.
import UIKit
import PromiseKit
struct User {
var firstname : String?
var lastname : String?
}
struct APIError {
var message : String?
}
class ViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let userPromise : Promise = self.getUserDetails()
userPromise.then { user -> Void in
//print(user.f)
}
}
func getUserDetails()->Promise<User> {
return Promise<User> { resolve in
let user = User(firstname: "Scot", lastname: "dem")
if ((user.firstname?.count) != nil) {
resolve.fulfill(user)
} else {
let error = APIError(message: "User not valid")
resolve.reject(error as! Error)
}
}
}
}
Once I get the user details I want to make a full name, uppercase promises which are dependent on userPromise.
I would like to use multiple then, done, finally blocks. Just want to understand usage.
Why I'm getting an error here when we use userPromise.then { user -> Void in
what should I give inside the block
In PromiseKit 6, then can no longer return Void. This is mainly due to the tuplegate issue in Swift 4.
Quote from PromieKit 6 Release News
With PromiseKit our then did multiple things, and we relied on Swift
to infer the correct then from context. However with multiple line
thens it would fail to do this, and instead of telling you that the
situation was ambiguous it would invent some other error. Often the
dreaded cannot convert T to AnyPromise. We have a troubleshooting
guide to combat this but I believe in tools that just work, and when
you spend 4 years waiting for Swift to fix the issue and Swift doesn’t
fix the issue, what do you do? We chose to find a solution at the
higher level.
So we split then into then, done and map.
then is fed the previous promise value and requires you return a promise.
done is fed the previous promise value and returns a Void promise (which is 80% of chain usage)
map is fed the previous promise value and requires you return a non-promise, ie. a value.
Hence .then { (user) -> Void in is no longer valid and that's why you're getting an error.
It's now designed to return a Thenable like so:
userPromise.then { user -> Promise<User> in
//return yet another promise
}
The .then that used to return Void is now it's own .done.
i.e:
userPromise.done { (user) in
print(user)
}
So when you mix 'em up:
We get:
userPromise
.then { (user) -> Promise<User> in
//return another Promise
return self.uppercasePromise(on: user)
}
.done { (user) in
/*
Depending on your sequence, no more promises are left
and you should have a matured user object by now
*/
print(user)
}
.catch { (error) in
print(error)
}
.finally {
print("finally")
}
func uppercasePromise(on user: User) -> Promise<User> {
/*
Didn't understand your statement but do whatever you meant when you said:
"Once I get the user details I want to make a full name, "
uppercase promises which are dependent on userPromise."
*/
return Promise { seal in
seal.fulfill(user)
}
}
I have a Completable being returned from a simple function.
This is not an async call, so I just need to return a succcessful completion or error depending on a conditional (using Rx here so I can tie into other Rx usages):
func exampleFunc() -> Completable {
if successful {
return Completable.just() // What to do here???
} else {
return Completable.error(SomeErrorType.someError)
}
}
The error case works pretty easily, but am having a block on how to just return a successful completable (without needing to .create() it).
I was thinking I just need to use Completable's .just() or .never(), but just is requiring a parameter, and never doesn't seem to trigger the completion event.
.empty() is the operator I was looking for!
Turns out, I had mixed up the implementations of .never() and .empty() in my head!
.never() emits no items and does NOT terminate
.empty() emits no items but does terminates normally
So, the example code above works like this:
func exampleFunc() -> Completable {
if successful {
return Completable.empty()
} else {
return Completable.error(SomeErrorType.someError)
}
}
Here is the documentation on empty/throw/never operators.
I would be more inclined to do the following:
func example() throws {
// do something
if !successful {
throw SomeErrorType.someError
}
}
Then in order to tie it into other Rx code, I would just use map as in:
myObservable.map { try example() }
But then, mapping over a Completable doesn't work because map's closure only gets called on next events. :-(
I tend to avoid Completable for this very reason, it doesn't seem to play well with other observables. I prefer to use Observable<Void> and send an empty event before the completed...
Something like this:
let chain = Observable<Void>.just()
let foo = chain.map { try example() }
foo.subscribe { event in print(event) }