Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cancellation Token #64

Closed
ghost opened this issue Jul 31, 2018 · 6 comments
Closed

Cancellation Token #64

ghost opened this issue Jul 31, 2018 · 6 comments

Comments

@ghost
Copy link

ghost commented Jul 31, 2018

current code:

    public static func download(token: Cancellation.Token? = nil) -> Promise<Data> {
        return Promise { fulfill, reject in
            // simulate download ..., typically data will come in chunks and will be cached ...  
            DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
                if let token = token, token.isCancellationRequested {
                    return reject(Error.cancel)
                } else {
                    return fulfill(Data())
                }
            })
        }
    }
    let source = Cancellation.Source()
    
    let promise = download(token: source.token)
        .then({ (data) in
            print("data -----> ", data)
        })
        .catch({ (error) in
            print("ERROR -> ", error)
        })
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
        // simulate user pressed stop downloading ...
        source.cancel()
    })

proposal:

implement something like this: https://github.com/vadymmarkov/When#fail
or maybe reevaluate #31
Here is a sketch: https://gist.github.com/umbri/7413f4c9b41cfc210561e01b6165a23e

Other suggestions ?

@ghost ghost changed the title Cancelation Token Cancellation Token Jul 31, 2018
@ghost
Copy link
Author

ghost commented Jul 31, 2018

#5

@shoumikhin
Copy link
Contributor

Hi @umbri, sorry, I don't completely understand. Do you propose to reject a promise with some predefined cancelled error and provide a special catch version to handle only those? In the example above a simple shared bool flag seems sufficient...

@ghost
Copy link
Author

ghost commented Aug 8, 2018

@shoumikhin Hi,
let me show a real world example: (simplified)

     func testPromise() -> Promise<[Icon]> {
        return Promise<[Icon]>(on: .global(qos: .userInitiated)) {
            return self.loadHtml()
                .then({ (html) -> Promise<[URL]> in
                    return self.parseHtml(with: html)
                })
                .then({ (urls) -> Promise<[Icon]> in
                    return Promises.any([
                        self.faviconByUrl(urls: urls),
                        Promises.all([
                            self.faviconSmall(),
                            self.faviconAppleTouch()
                        ])
                    ])
                    .then({ (result: [Maybe<[Icon]>]) -> Promise<[Icon]> in
                        fatalError() // do some transformations here ...
                    })
                })
        }
    }
    
    func loadHtml() -> Promise<String> {
        fatalError()
    }
    
    func faviconAppleTouch() -> Promise<Icon> {
        fatalError()
    }
    
    func faviconSmall() -> Promise<Icon> {
        fatalError()
    }
    
    func faviconByUrl(urls: [URL]) -> Promise<[Icon]> {
        return Promises.all(urls.map { self.download(url: $0) })
    }
    
    func parseHtml(with: String) -> Promise<[URL]> {
        fatalError()
    }
    
    func download(url: URL) -> Promise<Icon> {
        fatalError()
    }

imagine you have something like this, and you don't know when user will cancel all this promise chain testPromise, we need a simple method to do this

@ghost
Copy link
Author

ghost commented Aug 8, 2018

I see 2 approaches here:

  1. something like this https://github.com/vadymmarkov/When#fail , but we must have access to promise state discussed here: Change Promise properties: isPending, isFulfilled, isRejected from In… #31 to know when to reject the long running promise, imagine: download will come in chunks, and promise is already started, we can get some chunks and the check if promise was rejected and reject downloadTask
  2. something like this:
    https://gist.github.com/umbri/7413f4c9b41cfc210561e01b6165a23e, will be used like this:
func testIntegration2() {
        let token = CancellationToken()
        
        self.testPromise(token: token)
            .then { (icons) in
                // on success
            }
            .catch { (error) in
                // on fail or cancel
            }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            token.cancel() // simulate cancel
        }
    }
    
    func testPromise(token: CancellationToken? = nil) -> Promise<[Icon]> {
        return Promise<[Icon]>(on: .global(qos: .userInitiated)) {
            return self.loadHtml(token: token)
                .then({ (html) -> Promise<[URL]> in
                    return self.parseHtml(with: html)
                })
                .then({ (urls) -> Promise<[Icon]> in
                    return Promises.any([
                        self.faviconByUrl(urls: urls, token: token),
                        Promises.all([
                            self.faviconSmall(token: token),
                            self.faviconAppleTouch(token: token)
                        ])
                    ])
                    .then({ (result: [Maybe<[Icon]>]) -> Promise<[Icon]> in
                        fatalError() // do some transformations here ...
                    })
                })
            

        }
    }
    
    func loadHtml(token: CancellationToken? = nil) -> Promise<String> {
        fatalError()
    }
    
    func faviconAppleTouch(token: CancellationToken? = nil) -> Promise<Icon> {
        fatalError()
    }
    
    func faviconSmall(token: CancellationToken? = nil) -> Promise<Icon> {
        fatalError()
    }
    
    func faviconByUrl(urls: [URL], token: CancellationToken? = nil) -> Promise<[Icon]> {
        return Promises.all(urls.map { self.download(url: $0) })
    }
    
    func parseHtml(with: String) -> Promise<[URL]> {
        fatalError()
    }
    
    func download(url: URL, token: CancellationToken? = nil) -> Promise<Icon> {
        return Promise {
            let request = URLRequest(url: url)
            let task = URLSession(configuration: .ephemeral).dataTask(with: request) { data, response, error in
                // fulfill or resolve
            }
            
            try token?.onCancel {
                task.cancel()
            }
            
            task.resume()
        }
    }

@shoumikhin
Copy link
Contributor

Hi @umbri, did you see this example? Does it look like something you may find useful?

@ghost
Copy link

ghost commented Nov 3, 2018

Hi @umbri, thank you for the discussion. Please reopen if you have any additional comments or questions.

@ghost ghost closed this as completed Nov 3, 2018
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant