Search code examples
iosswiftswift3promisepromisekit

Returning promise that uses another promise with PromiseKit


Here's an example of the code this question is about:

func executeTask() {
    fetchApiData().then { foos in
        return filterData(foos) 
    }.then { foos in
        return saveData(foos)
    }.catch {
        /** handle error **/
        }
}

func fetchApiData() -> Promise<[Foo]> {
    return Promise<[Foo]> { fulfil, reject in
        /* Do a network request, and run through object mapper */
        fulfil( myFooCollection )
    }
}

func filterData(_ data: [Foo]) -> Promise<[Foo]> {

    return Promise<[Foo]> { fulfil, _ in

        _ = getIdsToFilter().then { ids -> Void in

            let filteredData = data.filter { foo in
                return ids.contains(foo.id)
            }

            fulfil(filteredData)

        }

    }

}

func getIdsToFilter() -> Promise<[Int]> {
    return Promise<[Int]> { fulfil, _ in 
        /* Do some task here */
        fulfil([10,20,30,40])
    }
}

func saveData(_ data: [Foo]) -> Promise<[Foo]> {
    return Promise<[Foo]> { fulfil, reject in 
            /* Do some save task here */
            fulfil(data)
        }
}

The function in particular I'm querying is the filterData one.

This is a pretty simple promise chain, get some data, filter it, then save it. However the filtering process requires the response from another promise before it can do it's thing.

As you can see, i've implemented this as a type of wrapper. I return the promise I need, but that promise calls another one from within itself, before doing it's thing.

I feel like this is a bit ugly and goes against the whole composition idea of Promises. I'm thinking there should be a way of doing something like this instead:

func filterData(_ data: [Foo]) -> Promise<[Foo]> {

    return getIdsToFilter().then { ids in
        return data.filter { foo in
            return ids.contains(foo.id)
        }
    }

}

However that of course doesn't work as xcode says:

Cannot convert return expression of type '[Foo]' to return type 'Promise<[Foo]>' (aka 'Promise<Array<Foo>>')

So... is there a way of flattening out that function? Or maybe what i'm doing is correct and that's how it should be?

P.S: I'm not looking for some overly complex, over-engineered solution to flattening that out. I'm just trying to follow some sort of best practice, being nested isn't the problem, I just want to be doing things "properly"


Solution

  • Function func filterData(_ data: [Foo]) -> Promise<[Foo]> expects to return promise. In you case the expression

    data.filter { foo in
            return ids.contains(foo.id)
    }
    

    returns Array of Foo objects. You have to wrap [Foo] to Promise object. See the example below

    func filterData(_ data: [Foo]) -> Promise<[Foo]> {
        return Promise { _ in
            return getIdsToFilter().then { ids in
                return Promise(value: data.filter { return ids.contains($0.id) })
            }
        }
    }
    

    Update: To understand what is wrong you can explicit declare the type of return parameters. Then you will see what type expects swift compiler and what type expect you. This tactic helps me understand compiler errors

    func filterData(_ data: [Foo]) -> Promise<[Foo]> {
        return Promise { _ in
            return getIdsToFilter().then { ids -> Promise<[Foo]> in
                return Promise(value: data.filter { return ids.contains($0.id) })
            }
        }
    }