Skip to content

Pitch: V7 chain-specific default dispatchers #1023

@GarthSnyder

Description

@GarthSnyder

In RxSwift, dispatching is an attribute of the chain. You switch dispatchers with a separate chain operator, e.g.

observable
    .observeOn(MainScheduler.instance)
    .subscribe { event in
        ...
    }
    ...

I believe ReactiveSwift works similarly, but I'm not as familiar with that library.

I love PromiseKit's simple convention of requiring an explicit dispatcher specification for each step in a chain unless you want to use the global default. It's direct and easily understood. I wouldn't want to change this system as the default.

Nevertheless, there's no reason why PromiseKit couldn't also, optionally, allow you to set a default dispatcher for a chain, and there are a couple of nice benefits that would come from this ability.

I'll mention some of the advantages below, but let me first clarify exactly what I'm talking about. Here's a straw-man proposal:

  • Promises gain setDefaultMapDispatcher and setDefaultReturnDispatcher methods (terrible placeholder names...) with a signature something like
public func setDefaultMapDispatcher(_ on: Dispatcher? = nil) -> Promise<T>
  • As usual, the returned promise is different from the original; it's a wrapper. Only subsequent chain elements added via the wrapper see the new default.

  • Any subsequent method uses the specified default dispatcher if nothing more specific is requested. conf.Q still exists but is consulted only if no chain-specific default has been set.

  • Methods that return new promises silently propagate the default dispatcher to them.

Benefits

  • Clearer and more self-documenting than conf.Q. I would hazard a guess that some users initially assume that conf.Q is consulted at the point of actual dispatch. But in fact, it's consulted at chain construction time through the default argument mechanism. This is useful because it allows different conf.Q values to be set for different regions of code, but it's not very discoverable without experimentation.

  • Chains don't affect each other. By contrast, there's only one conf.Q, and code has to take explicit precautions (save/restore) to avoid fighting over it.

  • Code is cleaner without repeating on: for every clause.

  • Purely additive: nothing changes unless you use the feature.

  • Facilitates debugging because you can easily set a temporary default for any portion of a chain. It's one line added or removed which affects no other code. With conf.Q, you have to add a variable assignment at the start of the chain, stop in the middle of the chain to set conf.Q, then resume the chain from the variable.

  • The big one: it allows functions that yield or propagate promises to specify (suggest, really) dispatching policy. Result types that aren't thread-safe can return themselves with dispatching set to a serial queue. For example, a routine that returns a Promise<NSManagedObject> might set the chain's default dispatcher to a CoreDataDispatcher for the appropriate NSManagedObjectContext so that the managed object can be freely manipulated with no additional code on the receiver's end. E.g.,

fetchUser(managerID).map { manager in
    (manager.firstName, manager.lastName)
}.done { name in
    button.setTitle("Email \(name.0) \(name.1)", for: .normal)
}

Thoughts?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions