Swift - multiple Chain http request with loop

半世苍凉 提交于 2020-01-02 16:18:20

问题


Since 2 days it feels that I'm searching the whole web to solve my problem with multiple http requests. So my workflow looks like this:

  1. Upload a image to a server

    • Response = XML Format with a Task ID
  2. GET request to the server with the Task ID to check the status of this task.

    • Response = XML Format where the status could be "Completed", "In Progress", "Queued"
    • If Status != "Completed" - retry step 2
    • If Status == "Completed" - go to step 3
  3. Download the result from the resultUrl

My last try was to use PromiseKit to chain the requests in a clean way like described in this post: Chain multiple Alamofire requests. But I don't know how to loop the second step every 2-5 seconds if the status isn't completed yet.

Is there a recommended solution for this workflow? This was my test with PromiseKit, where i successfully chained the requests without a loop:

let request = Client.imageUploadRequest(image: imageView.image!)
let httpOperation = HTTPOperation(withRequest: request)

httpOperation.sendRequest().then() { result -> Promise<String> in
    let xml = SWXMLHash.parse(result)
    let id = self.getXMLAttribute(from: xml, with: "id")!
    let taskStatusrequest =  Client.getTaskStatusRequest(withTaskID: id)
    let httpOperation = HTTPOperation(withRequest: taskStatusrequest)

    return httpOperation.sendRequest()
}
// Loop this result if status != "Completed"
.then { result -> Promise<Data> in
    let xml = SWXMLHash.parse(result)
    let downloadUrl = self.getXMLAttribute(from: xml, with: "resultUrl")!
    let downloadRequest = Client.getDownloadRequest(withUrl: downloadUrl)
    let httpOperation = HTTPOperation(withRequest: downloadRequest)

    // if status != "Completed" don't return, retry this step
    return httpOperation.downloadData()
}
.then { _ -> Void in
    // Refresh View with data
}

回答1:


The basic idea would be to write a routine that retries the request in question, and only fulfills the promise if certain criteria are met:

/// Attempt a network request.
///
/// - Parameters:
///   - request:    The request.
///   - maxRetries: The maximum number of attempts to retry (defaults to 100).
///   - attempt:    The current attempt number. You do not need to supply this when you call this, as this defaults to zero.
///   - fulfill:    The `fulfill` closure of the `Promise`.
///   - reject:     The `reject` closure of the `Promise.

private func retry(_ request: URLRequest, maxRetries: Int = 100, attempt: Int = 0, fulfill: @escaping (Data) -> Void, reject: @escaping (Error) -> Void) {
    guard attempt < maxRetries else {
        reject(RetryError.tooManyRetries)
        return
    }

    Alamofire.request(request)
        .validate()
        .responseData { response in
            switch response.result {
            case .success(let value):
                let taskCompleted = ...          // determine however appropriate for your app
                let serverReportedFailure = ...

                if serverReportedFailure {
                    reject(RetryError.taskFailed)
                } else if taskCompleted {
                    fulfill(value)
                } else {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
                        self.retry(request, maxRetries: maxRetries, attempt: attempt + 1, fulfill: fulfill, reject: reject)
                    }
                }
            case .failure(let error):
                reject(error)
            }
    }
}

/// Error codes for retrying of network requests.

enum RetryError: Error {
    case tooManyRetries
    case taskFailed
}

You can then have a method that creates the promise that is satisfied by the above:

/// Create a promise for a network request that will be retried until
/// some criteria is met.
///
/// - Parameter request: The request to be attempted.
/// - Returns: The `Promise`.

private func retry(for request: URLRequest) -> Promise<Data> {
    return Promise { fulfill, reject in
        self.retry(request, fulfill: fulfill, reject: reject)
    }
}

You can now do the standard Promise stuff with the above, e.g.:

retry(for: request).then { data in
    print("received \(data)")
}.catch { error in
    print("error: \(error)")
}

A few caveats in the above:

  • I'm calling fulfill with a Data. Usually you'd have some model object or the like, but I'm not sure what was appropriate in your case.

  • I'm obviously not doing any XML parsing. But you'd obviously parse the response and determine taskCompleted accordingly.

  • Although you said the status of the task could either be "queued", "in progress", and "completed", I assumed you'll eventually have your server's process adding some error handling if the queued task failed. So, I also added a taskFailed error. Do as you see fit.

  • I made some assumptions about maximum number of retries and what to do if there was a network error. Clearly, customize these conditions to whatever is appropriate in your case.

So, don't get lost in the details of my example, but rather focus on the broader picture, namely that you can have a routine that creates a Promise and passes the two fulfill and reject closures to a separate routine that actually performs the retry logic.




回答2:


See chaining https-requests without Alamofire: POST+GET+GET+GET...

class ViewController1: UIViewController, URLSessionDataDelegate {
    var URLSessionConfig :URLSessionConfiguration!
    var session: URLSession?
    var task0: URLSessionTask!
    var task1: URLSessionTask!
    var task2: URLSessionTask!
    var task3: URLSessionTask!

    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        self.URLSessionConfig = URLSessionConfiguration.ephemeral
        #if available
        self.URLSessionConfig.waitsForConnectivity = true
        #endif
        self.session = URLSession(configuration: URLSessionConfig, delegate: self, delegateQueue: OperationQueue.main)
    }

    func Start() {
        let url0 = URL(string: "https://myserver/PRIMARY_PATH/")!
        var req0 = URLRequest(url: url0)
        req0.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
        req0.httpMethod = "POST"
        req0.httpBody = str.data(using: .utf8)
        self.task0 = self.session?.dataTask(with: req0 as URLRequest)
        self.task0.resume()
    }

    func parseData0(didReceive data: Data) -> URLRequest? {
        do {
            let json = try JSONSerialization.jsonObject(with: data) as? [String: String]
            ...
            let url1 = URL(string: "https://myserver/SECONDARY_PATH/"+...)!
            var req1 = URLRequest(url: url1)
            req1.httpMethod = "GET"            
            return req1
        }
        catch let parseError {
            debugPrint("parsing error: \(parseError)")
            return nil
        }
    }
    func parseData1(didReceive data: Data) -> URLRequest? {
        do {
            let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
            ...
        }
        catch let parseError {
            debugPrint("parsing error: \(parseError)")
            return nil
        }
    }

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        debugPrint("Data received: \(data)")
        if dataTask == self.task0 {
            let req1: URLRequest? = parseData0(didReceive: data)
            if req1 != nil {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                    self.task1 = self.session?.dataTask(with: req1!)
                    self.task1.resume()
                }
            }
        }
        if dataTask == self.task1 {
            let req1: URLRequest? = parseData1(didReceive: data)
            if req1 != nil {
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                    self.task2 = self.session?.dataTask(with: req1!)
                    self.task2.resume()
                }
            }
        }
        if dataTask == self.task2 {
            let req1: URLRequest? = parseData1(didReceive: data)
            if req1 != nil {
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                    self.task3 = self.session?.dataTask(with: req1!)
                    self.task3.resume()
                }
            }
        }
        if dataTask == self.task3 {
            let req1: URLRequest? = parseData1(didReceive: data)
            if req1 != nil {
                ...
            }
        }
    }

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {        
        debugPrint("Response received: \(response)")
        completionHandler(URLSession.ResponseDisposition.allow)
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if error != nil {
            debugPrint("error message: \(error!)")
            debugPrint("code: \(error!._code)")
        }
    }

    func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
        debugPrint("there was an error: \(error?.localizedDescription ?? "")")
    }
}


来源:https://stackoverflow.com/questions/40690726/swift-multiple-chain-http-request-with-loop

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!