问题
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:
Upload a image to a server
- Response = XML Format with a Task ID
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
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 aData
. 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