google / promises

Promises is a modern framework that provides a synchronization construct for Swift and Objective-C.
Apache License 2.0
3.8k stars 293 forks source link

Cancellation Token #64

Closed ghost closed 5 years ago

ghost commented 6 years ago

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 commented 6 years ago

5

shoumikhin commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

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: https://github.com/google/promises/pull/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 commented 5 years ago

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

ghost commented 5 years ago

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