Git Product home page Git Product logo

Comments (4)

shoumikhin avatar shoumikhin commented on April 28, 2024

Hi Teerapap, that sounds like an interesting idea!

Do you suggest that we parametrize the Promise.pending (FBLPromise.pendingPromise) constructor with a custom queue arg having DispatchQueue.promises default value, store it within the promise and use in all convenience APIs that we currently have defaulting to DispatchQueue.promises? That way once the promise is created with a custom queue, everything you chain on it would be dispatched on that custom queue you've specified initially.

Although, you'd need to explicitly fall back to the main queue at some point of the chain to update UI, for example. Also, make sure other users of your promise are aware anything they chain on it will be dispatched on some custom queue.

Is that what you'd expect?

from promises.

teerapap avatar teerapap commented on April 28, 2024

Hi shoumikhin,

Yes. Correct. Maybe explain with code is easier

Currently my code looks like this.

func callAPI(userId: String, on queue: DispatchQueue) -> Promise<Result> {
    return Promise<Result> (on: queue) {
        // call api
    }.then(on: queue) {
        return Result()
    }
}

func register(user: String, on queue: DispatchQueue) -> Promise<Result> {
    return Promise(on: queue) {
        // do something
        return db.getUserId(user)
    }.then(on: queue) { userId -> Promise<Result> in
        return callAPI(userId: userId, on: queue)
            .always(on: queue) {
                
            }
    }.recover(on: queue) { error in
        // do some error conversion
    } // more then closures
}

register("username", on: DispatchQueue.global(qos: .userInitiated))
.always(on: DispatchQueue.main) {
    // update UI on main queue
}.catch(on: DispatchQueue.main) {
    // show error UI on main queue
}.then(on: DispatchQueue.main) {
    // update UI  on main queue
}

It would be better if I don't have to pass the queue down the line like this

func callAPI(userId: String, on queue: DispatchQueue) -> Promise<Result> {
    return Promise<Result> (on: queue) {
       // ....
    }.then {
        // run in the same queue as above
        return Result()
    }
}

func register(user: String, on queue: DispatchQueue) -> Promise<Result> {
    return Promise(on: queue) {
        // do something
        return db.getUserId(user)
    }.then { userId -> Promise<Result> in
       // run on the same queue as above

        return callAPI(userId: userId, on: queue)
            .always {
                // run on the same queue as specified in callAPI(...)
            }
    }.recover { error in
         // run on the same queue as above
    }
}

register("username", on: DispatchQueue.global(qos: .userInitiated))
.always(on: DispatchQueue.main) {
    // update UI
}.catch(on: DispatchQueue.main) {
    // show error UI
}.then(on: DispatchQueue.main) {
    // update UI
}

from promises.

shoumikhin avatar shoumikhin commented on April 28, 2024

Hi Teerapap,

The thing is setting a queue per promise and using it for the rest of the chain is slightly trickier than it may seem initially. That approach is potentially quite error prone, since you're gonna pass a promise around and chain other blocks on it without clearly knowing which queue it’s run on.

I'd recommend to be explicit in each block, or rely on the well defined default, especially if the chain spans multiple methods throughout a class, or maybe even across classes. Otherwise, I imagine, in future you'd end up having some asserts or checks in each closure to guarantee it's indeed dispatched on a specific queue and other collaborators didn't suddenly mess things up.

Also, such change may introduce some issues with the existing code:

promise.then(on: queue) { /*...*/ }.then { /*...*/ }

The second then above used to be dispatched on main by default, but with the proposed change it's gonna be dispatched on a background queue, which would be quite surprising for other clients.

Generally, it'd be very beneficial to follow a pattern when DB access, networking and other async APIs are always initiated from the main queue, but then internally use some dedicated background queues to do the real work. For example, you may have something like:

extension DispatchQueue {
  static let diskIO: DispatchQueue = { DispatchQueue.global() }()
  static let networkIO: DispatchQueue = { DispatchQueue.global() }()
  static let utility: DispatchQueue = { DispatchQueue.global() }()
}

func callAPI(userId: String) -> Promise<Result> {
  assert(Thread.isMainThread)
  return Promise<Result>(on: .networkIO) {
    // call API
  }  
}

func getUserId(_ user: String) -> Promise<UserId> {
  assert(Thread.isMainThread)
  return Promise<UserId>(on: .diskIO) {
    // fetch records from DB
  }  
}

func register(user: String) -> Promise<Result> {
  assert(Thread.isMainThread)
  db.getUserId(user).then(callAPI).always(on: .utility) {
    // ...
  }.recover(on: .utility) { error in
    // do some error conversion
  } // more then closures
}

register("username").always {
  // update UI on main queue
}.catch { error in
  // show error UI on main queue
}.then { value in
  // update UI  on main queue
}

Please let us know if this is unclear or if you have any additional questions.

Thank you!

from promises.

teerapap avatar teerapap commented on April 28, 2024

Thanks for the explanation. I understand my suggestion will have issue with existing code.
and I understand your point of being explicit.

I don't think it will be error prone though because if I want to make sure a closure runs on specific queue, I would specify explicitly at the closure anyway to run on that queue.

The situation is sometimes I want to chain a closure that does not matter which queue it runs on. Ex.

.recover { error in 
// convert error to some new error
throw newError
}

For this case, I think running it on the queue the pending promise running on (ex. .userInitiated or whatever) is more suitable than relying on default (which is usually main queue and high priority).

Your approach with .utility, .networkIO, .diskIO queue is also acceptable.

Anyway, breaking the existing assumption is bad so please take it as a feedback for the next time.
Thanks

from promises.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.