CKPromise.Swift 0.2

CKPromise.Swift 0.2

测试已测试
Lang语言 SwiftSwift
许可证 MIT
发布最后发布2016年7月
SPM支持 SPM

Maintained by Cristian Kocza.



  • Cristian Kocza

CKPromise.Swift

Swift 实现Promises/A+提议的尝试,完全支持泛型,以便能够利用 Swift 强大的类型系统。完整的规范可以在 http://promisesaplus.com/ 找到。

实现尝试尽可能地遵循 Promise/A+ 规范,但是受 Swift 强类型系统的限制,并非全部都能遵循。

当前实现尚未实现循环承诺链检测,将在以后添加对此的支持。

安装

通过 GitHub

  1. git clone https://github.com/cristik/CKPromise.Swift.git
  2. CKPromise.Swift.xcproject 添加到您的项目/工作区
  3. 链接到 CKPromise_Swift 目标

用法

让我们看看承诺的实际效果。我们从一个简单的任务开始 - 发送一个 NSURLSession 请求,并将接收到的数据解析到字典中。

首先,让我们扩展 NSURLSessionNSData 以为其提供发送请求和解析 JSON 的承诺支持

extension NSURLSession {
    func sendRequest(request: NSURLRequest) -> Promise<NSData,NSError> {
        let promise = Promise<NSData,NSError>()
        let task = self.dataTaskWithRequest(request) { data, urlResponse, error in
            if let error = error {
                // we have an error, means the request failed, reject promise
                promise.reject(error)
            } else if let data = data {
                // we don't have an error and we have data, resolve promise
                promise.resolve(data)
            } else {
                // we have neither error, nor data, report a generic error
                // another approach would have been to resolve the promise
                // with an empty NSData object
                promise.reject(NSError.genericError())
            }
        }
        task.resume()
        return promise
    }
}

extension NSData {
    func parseJSON() -> Promise<[NSObject:AnyObject], NSError> {
        let promise = Promise<[NSObject:AnyObject], NSError>()
        if let parsedJSON = try? NSJSONSerialization.JSONObjectWithData(self, options: []),
            let result = parsedJSON as? [NSObject:AnyObject] {
            // yay, we were able to parse, and received a dictionary
            promise.resolve(result)
        } else {
            // :( report an invalid json error
            promise.reject(NSError.invalidJSONError())
        }
        return promise
    }
}

我们可以像以下这样使用上面的扩展:

let request = NSURLRequest(URL: NSURL(string: "https://jsonplaceholder.typicode.com/posts/1")!)
NSURLSession.sharedSession().sendRequest(request).onSuccess({
    return $0.parseJSON()
}).onSuccess( {
    print("Parsed JSON: \($0)")
}).onFailure( {
    print("Failed with error: \($0)")
})

sendRequest 委托成功地回调返回另一个承诺,即 JSON 解析承诺,这使我们能够很好地连接承诺。如果两个承诺中的任何一个失败,执行将直接转到最后的失败处理程序,这有助于我们,因为我们不需要编写多个失败处理程序。

这看起来似乎不多,所以让我们再增加一步:从解析的字典中创建一个 Post 实体。以下是关于承诺方面的 Post 的可能实现:

struct Post {
    private(set) var id: Int = 0
    private(set) var userId: Int = 0
    private(set) var title: String = ""
    private(set) var body: String = ""

    static func fromDictionary(dictionary: [NSObject:AnyObject]) -> Promise<Post,NSError> {
        let promise = Promise<Post,NSError>()
        guard let id = dictionary["id"] as? Int,
            userId = dictionary["userId"] as? Int else {
                promise.reject(NSError.invalidDictionaryError())
                return promise
        }
        var post = Post()
        post.id = id
        post.userId = userId
        post.title = dictionary["title"] as? String ?? ""
        post.body = dictionary["body"] as? String ?? ""

        promise.resolve(post)

        return promise
    }
}

基本上,我们添加了对从字典创建 Post 的支持,采用一种承诺风格的方式。我们如何使用它?很简单:

let request = NSURLRequest(URL: NSURL(string: "https://jsonplaceholder.typicode.com/posts/1")!)
NSURLSession.sharedSession().sendRequest(request).onSuccess({
        return $0.parseJSON()
    }).onSuccess({
        return Post.fromDictionary($0)
    }).onSuccess({
        print("Parsed post: \($0)")
    }).onFailure( {
        print("Failed with error: \($0)")
    })

我们可以进一步扩展承诺,直到我们所需。

现在,让我们回到 NSURLSession。还记得 sendRequest 方法吗?如果我们还想返回数据及其 URL 响应,该怎么办?这并不难,多亏了元组。

extension NSURLSession {
    func sendRequest(request: NSURLRequest) -> Promise<(NSURLResponse, NSData),NSError> {
        let promise = Promise<(NSURLResponse, NSData),NSError>()
        let task = self.dataTaskWithRequest(request) { data, urlResponse, error in
            if let error = error {
                promise.reject(error)
            } else if let data = data, urlResponse = urlResponse {
                promise.resolve((urlResponse, data))
            } else {
                promise.reject(NSError.genericError())
            }
        }
        task.resume()
        return promise
    }
}

现在,大多数时候我们会发送 HTTP 请求,如果我们能够无需在回调用中降级到强制转换就能使用 NSHTTPURLResponse 子类,那将是件好事。类似如下所示:

func sendHTTPRequest(request: NSURLRequest) -> Promise<(NSHTTPURLResponse, NSData),NSError>

做这件事并不难,但首先我们需要对 sendRequest 方法稍作调整

extension NSURLSession {
    func sendRequest<T: NSURLResponse)(request: NSURLRequest) -> Promise<(T, NSData),NSError> {
        let promise = Promise<(T, NSData),NSError>()
        let task = self.dataTaskWithRequest(request) { data, urlResponse, error in
            if let error = error {
                promise.reject(error)
            } else if let data = data, urlResponse = urlResponse as? T {
                promise.resolve((urlResponse, data))
            } else {
                promise.reject(NSError.genericError())
            }
        }
        task.resume()
        return promise
    }

    func sendHTTPRequest(request: NSURLRequest) -> Promise<(NSHTTPURLResponse, NSData),NSError> {
        return sendRequest(request)
    }
}

这就像那样简单,多亏了泛型的支持。

但是等等,如果URL请求不对应于HTTP请求怎么办?在这种情况下,我们可能想要快速失败,甚至不发送请求,而不是在收到服务器响应后在进行降阶步骤时失败。嗯,正如你所猜测的,这也不难。

func sendHTTPRequest(request: NSURLRequest) -> Promise<(NSHTTPURLResponse, NSData),NSError> {
    guard ["http", "https"].contains(request.url.scheme) else {
        return Promise.rejected(NSError.invalidRequestError())
    }
    return sendRequest(request)
}

承诺的另一个常见情况是恢复失败。一个虚构的例子是在资源失败时,使用PUT尝试恢复失败的POST操作。这就是如何实现这种情况。

let postRequest = NSURLRequest(...)
let putRequest = NSURLRequest(...)
NSURLSession.sharedSession().sendRequest(postRequest).onFailure({
    // if the post request fails, try with a put one
    return NSURLSession.sharedSession().sendRequest(putRequest)
}).onSuccess({
    // we end up here in two cases: either the post request succeeded, or it failed
    // and the put one succeeded
}).onFailure({
    // both requests failed
})

再次,承诺允许我们以线性流程(我说得更自然一点)声明数据处理。