Chain multiple Alamofire requests

Wrapping other asynchronous stuff in promises works like this:

func myThingy() -> Promise<AnyObject> {
    return Promise{ fulfill, reject in
        Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]).response { (_, _, data, error) in
            if error == nil {
                fulfill(data)
            } else {
                reject(error)
            }
        }
    }
}

Edit: Nowadays, use: https://github.com/PromiseKit/Alamofire-


I wrote a class which handles a chain of request one by one.

I created a class RequestChain wich takes Alamofire.Request as parameter

class RequestChain {
    typealias CompletionHandler = (success:Bool, errorResult:ErrorResult?) -> Void

    struct ErrorResult {
        let request:Request?
        let error:ErrorType?
    }

    private var requests:[Request] = []

    init(requests:[Request]) {
        self.requests = requests
    }

    func start(completionHandler:CompletionHandler) {
        if let request = requests.first {
            request.response(completionHandler: { (_, _, _, error) in
                if error != nil {
                    completionHandler(success: false, errorResult: ErrorResult(request: request, error: error))
                    return
                }
                self.requests.removeFirst()
                self.start(completionHandler)
            })
            request.resume()
        }else {
            completionHandler(success: true, errorResult: nil)
            return
        }

    }
}

And I use it like this

let r1 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
    print("1")
}

let r2 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
    print("2")
}

let r3 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
    print("3")
}

let chain = RequestChain(requests: [r1,r2,r3])

chain.start { (success, errorResult) in
    if success {
        print("all have been success")
    }else {
        print("failed with error \(errorResult?.error) for request \(errorResult?.request)")
    }


}

Importent is that you are telling the Manager to not execute the request immediately

    let manager = Manager.sharedInstance
    manager.startRequestsImmediately = false

Hope it will help someone else

Swift 3.0 Update

class RequestChain {
    typealias CompletionHandler = (_ success:Bool, _ errorResult:ErrorResult?) -> Void

    struct ErrorResult {
        let request:DataRequest?
        let error:Error?
    }

    fileprivate var requests:[DataRequest] = []

    init(requests:[DataRequest]) {
        self.requests = requests
    }

    func start(_ completionHandler:@escaping CompletionHandler) {
        if let request = requests.first {
            request.response(completionHandler: { (response:DefaultDataResponse) in
                if let error = response.error {
                    completionHandler(false, ErrorResult(request: request, error: error))
                    return
                }

                self.requests.removeFirst()
                self.start(completionHandler)
            })
            request.resume()
        }else {
            completionHandler(true, nil)
            return
        }

    }
}

Usage Example Swift 3

/// set Alamofire default manager to start request immediatly to false
        SessionManager.default.startRequestsImmediately = false
        let firstRequest = Alamofire.request("https://httpbin.org/get")
        let secondRequest = Alamofire.request("https://httpbin.org/get")

        let chain = RequestChain(requests: [firstRequest, secondRequest])
        chain.start { (done, error) in

        }

You have multiple options.


Option 1 - Nesting Calls

func runTieredRequests() {
    let putRequest = Alamofire.request(.PUT, "http://httpbin.org/put")
    putRequest.response { putRequest, putResponse, putData, putError in
        let getRequest = Alamofire.request(.GET, "http://httpbin.org/get")
        getRequest.response { getRequest, getResponse, getData, getError in
            // Process data
            // Reload table
        }
    }
}

This is definitely the approach I would recommend. Nesting one call into another is very simple and is pretty easy to follow. It also keeps things simple.


Option 2 - Splitting into Multiple Methods

func runPutRequest() {
    let putRequest = Alamofire.request(.PUT, "http://httpbin.org/put")
    putRequest.response { [weak self] putRequest, putResponse, putData, putError in
        if let strongSelf = self {
            // Probably store some data
            strongSelf.runGetRequest()
        }
    }
}

func runGetRequest() {
    let getRequest = Alamofire.request(.GET, "http://httpbin.org/get")
    getRequest.response { [weak self] getRequest, getResponse, getData, getError in
        if let strongSelf = self {
            // Probably store more data
            strongSelf.processResponse()
        }
    }
}

func processResponse() {
    // Process that data
}

func reloadData() {
    // Reload that data
}

This option is less dense and splits things up into smaller chunks. Depending on your needs and the complexity of your response parsing, this may be a more readable approach.


Option 3 - PromiseKit and Alamofire

Alamofire can handle this pretty easily without having to pull in PromiseKit. If you really want to go this route, you can use the approach provided by @mxcl.


Here is another way to do this (Swift 3, Alamofire 4.x) using a DispatchGroup

import Alamofire

    struct SequentialRequest {

        static func fetchData() {

            let authRequestGroup =  DispatchGroup()
            let requestGroup = DispatchGroup()
            var results = [String: String]()

            //First request - this would be the authentication request
            authRequestGroup.enter()
            Alamofire.request("http://httpbin.org/get").responseData { response in
            print("DEBUG: FIRST Request")
            results["FIRST"] = response.result.description

            if response.result.isSuccess { //Authentication successful, you may use your own tests to confirm that authentication was successful

                authRequestGroup.enter() //request for data behind authentication
                Alamofire.request("http://httpbin.org/get").responseData { response in
                    print("DEBUG: SECOND Request")
                    results["SECOND"] = response.result.description

                    authRequestGroup.leave()
                }

                authRequestGroup.enter() //request for data behind authentication
                Alamofire.request("http://httpbin.org/get").responseData { response in
                    print("DEBUG: THIRD Request")
                    results["THIRD"] = response.result.description

                    authRequestGroup.leave()
                }
            }

            authRequestGroup.leave()

        }


        //This only gets executed once all the requests in the authRequestGroup are done (i.e. FIRST, SECOND AND THIRD requests)
        authRequestGroup.notify(queue: DispatchQueue.main, execute: {

            // Here you can perform additional request that depends on data fetched from the FIRST, SECOND or THIRD requests

            requestGroup.enter()
            Alamofire.request("http://httpbin.org/get").responseData { response in
                print("DEBUG: FOURTH Request")
                results["FOURTH"] = response.result.description

                requestGroup.leave()
            }


            //Note: Any code placed here will be executed before the FORTH request completes! To execute code after the FOURTH request, we need the request requestGroup.notify like below
            print("This gets executed before the FOURTH request completes")

            //This only gets executed once all the requests in the requestGroup are done (i.e. FORTH request)
            requestGroup.notify(queue: DispatchQueue.main, execute: {

                //Here, you can update the UI, HUD and turn off the network activity indicator

                for (request, result) in results {
                    print("\(request): \(result)")
                }

                print("DEBUG: all Done")
            })

        })

    }
}

Details

  • Alamofire 4.7.2
  • PromiseKit 6.3.4
  • Xcode 9.4.1
  • Swift 4.1

Full Sample

NetworkService

import Foundation
import Alamofire
import PromiseKit

class NetworkService {

    static fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)

    fileprivate class func make(request: DataRequest) -> Promise <(json: [String: Any]?, error: Error?)> {
        return Promise <(json: [String: Any]?, error: Error?)> { seal in
            request.responseJSON(queue: queue) { response in

                // print(response.request ?? "nil")  // original URL request
                // print(response.response ?? "nil") // HTTP URL response
                // print(response.data ?? "nil")     // server data
                //print(response.result ?? "nil")   // result of response serialization

                switch response.result {
                case .failure(let error):
                    DispatchQueue.main.async {
                        seal.fulfill((nil, error))
                    }

                case .success(let data):
                    DispatchQueue.main.async {
                        seal.fulfill(((data as? [String: Any]) ?? [:], nil))
                    }
                }
            }
        }
    }

    class func searchRequest(term: String) -> Promise<(json: [String: Any]?, error: Error?)>{
        let request = Alamofire.request("https://itunes.apple.com/search?term=\(term.replacingOccurrences(of: " ", with: "+"))")
        return make(request: request)
    }
}

Main func

func run() {
    _ = firstly {
        return Promise<Void> { seal in
            DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + .seconds(2)) {
                print("1 task finished")
                DispatchQueue.main.async {
                    seal.fulfill(Void())
                }
            }
        }
        }.then {
            return NetworkService.searchRequest(term: "John").then { json, error -> Promise<Void> in
                print("2 task finished")
                //print(error ?? "nil")
                //print(json ?? "nil")
                return Promise { $0.fulfill(Void())}
            }
        }.then {_ -> Promise<Bool> in
            print("Update UI")
            return Promise { $0.fulfill(true)}
        }.then { previousResult -> Promise<Void> in
            print("previous result: \(previousResult)")
            return Promise { $0.fulfill(Void())}
    }
}

Result

enter image description here