Skip to content

Commit

Permalink
Call callbacks from the last context only
Browse files Browse the repository at this point in the history
  • Loading branch information
timvermeulen committed Nov 10, 2018
1 parent a67585b commit 5152cf8
Show file tree
Hide file tree
Showing 10 changed files with 60 additions and 58 deletions.
8 changes: 4 additions & 4 deletions Promise.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@
/* End PBXAggregateTarget section */

/* Begin PBXBuildFile section */
220A88DA21978534008FEB45 /* DispatchQueue+ExecutionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220A88D921978534008FEB45 /* DispatchQueue+ExecutionContext.swift */; };
OBJ_47 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* Atomic.swift */; };
OBJ_48 /* BasicFuture+Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* BasicFuture+Foundation.swift */; };
OBJ_49 /* BasicFuture+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* BasicFuture+Extras.swift */; };
OBJ_50 /* BasicFuture.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* BasicFuture.swift */; };
OBJ_51 /* BasicPromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* BasicPromise.swift */; };
OBJ_52 /* DispatchQueue+ExecutionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* DispatchQueue+ExecutionContext.swift */; };
OBJ_53 /* ExecutionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* ExecutionContext.swift */; };
OBJ_54 /* Future+Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* Future+Foundation.swift */; };
OBJ_55 /* Future+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* Future+Extras.swift */; };
Expand Down Expand Up @@ -69,11 +69,11 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
220A88D921978534008FEB45 /* DispatchQueue+ExecutionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+ExecutionContext.swift"; sourceTree = "<group>"; };
OBJ_11 /* BasicFuture+Foundation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BasicFuture+Foundation.swift"; sourceTree = "<group>"; };
OBJ_12 /* BasicFuture+Extras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BasicFuture+Extras.swift"; sourceTree = "<group>"; };
OBJ_13 /* BasicFuture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicFuture.swift; sourceTree = "<group>"; };
OBJ_14 /* BasicPromise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicPromise.swift; sourceTree = "<group>"; };
OBJ_15 /* DispatchQueue+ExecutionContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+ExecutionContext.swift"; sourceTree = "<group>"; };
OBJ_16 /* ExecutionContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExecutionContext.swift; sourceTree = "<group>"; };
OBJ_18 /* Future+Foundation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Future+Foundation.swift"; sourceTree = "<group>"; };
OBJ_19 /* Future+Extras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Future+Extras.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -123,8 +123,8 @@
isa = PBXGroup;
children = (
OBJ_9 /* Atomic.swift */,
OBJ_15 /* DispatchQueue+ExecutionContext.swift */,
OBJ_16 /* ExecutionContext.swift */,
220A88D921978534008FEB45 /* DispatchQueue+ExecutionContext.swift */,
OBJ_22 /* Result.swift */,
OBJ_23 /* Traversable.swift */,
);
Expand Down Expand Up @@ -312,11 +312,11 @@
buildActionMask = 0;
files = (
OBJ_47 /* Atomic.swift in Sources */,
220A88DA21978534008FEB45 /* DispatchQueue+ExecutionContext.swift in Sources */,
OBJ_48 /* BasicFuture+Foundation.swift in Sources */,
OBJ_49 /* BasicFuture+Extras.swift in Sources */,
OBJ_50 /* BasicFuture.swift in Sources */,
OBJ_51 /* BasicPromise.swift in Sources */,
OBJ_52 /* DispatchQueue+ExecutionContext.swift in Sources */,
OBJ_53 /* ExecutionContext.swift in Sources */,
OBJ_54 /* Future+Foundation.swift in Sources */,
OBJ_55 /* Future+Extras.swift in Sources */,
Expand Down
60 changes: 37 additions & 23 deletions Sources/Promise/BasicFuture/BasicFuture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,59 @@ public final class BasicFuture<Value> {
}

private extension BasicFuture {
struct Callback {
let changesContext: Bool
let callback: (Value) -> Void

func call(with value: Value, on context: ExecutionContext) {
if changesContext {
callback(value)
} else {
context { [callback] in callback(value) }
}
}
}

enum State {
case pending(callbacks: [(Value) -> Void])
case pending(callbacks: [Callback])
case fulfilled(with: Value)
}

convenience init(context: @escaping ExecutionContext, _ process: (BasicPromise<Value>) -> Void) {
self.init(context: context)
process(BasicPromise(future: self))
}

func addCallback(_ callback: Callback) {
let value = state.access { state -> Value? in
switch state {
case .pending(var callbacks):
state = .pending(callbacks: [])
callbacks.append(callback)
state = .pending(callbacks: callbacks)
return nil

case .fulfilled(let value):
return value
}
}

if let value = value {
callback.call(with: value, on: context)
}
}
}

internal extension BasicFuture {
func fulfill(with value: Value) {
let callbacks: [(Value) -> Void]? = state.access { state in
let callbacks: [Callback]? = state.access { state in
guard case .pending(let callbacks) = state else { return nil }

state = .fulfilled(with: value)
return callbacks
}

callbacks?.forEach { callback in
context { callback(value) }
}
callbacks?.forEach { $0.call(with: value, on: context) }
}

var testableValue: Value? {
Expand All @@ -56,29 +86,13 @@ public extension BasicFuture {

@discardableResult
func then(_ callback: @escaping (Value) -> Void) -> BasicFuture {
let value = state.access { state -> Value? in
switch state {
case .pending(var callbacks):
state = .pending(callbacks: [])
callbacks.append(callback)
state = .pending(callbacks: callbacks)
return nil

case .fulfilled(let value):
return value
}
}

if let value = value {
context { callback(value) }
}

addCallback(Callback(changesContext: false, callback: callback))
return self
}

func changeContext(_ context: @escaping ExecutionContext) -> BasicFuture {
return BasicFuture(context: context) { promise in
then(promise.fulfill)
addCallback(Callback(changesContext: true, callback: promise.fulfill))
}
}
}
4 changes: 2 additions & 2 deletions Sources/Promise/BasicFuture/BasicPromise.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
public final class BasicPromise<Value> {
let future: BasicFuture<Value>
internal let future: BasicFuture<Value>

init(future: BasicFuture<Value>) {
internal init(future: BasicFuture<Value>) {
self.future = future
}
}
Expand Down
6 changes: 1 addition & 5 deletions Sources/Promise/Future/Future+Extras.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,7 @@ public extension Collection {
}

public extension Future {
private func _always(_ handler: @escaping () -> Void) {
always(handler)
}

func waiting<T>(for other: Future<T>) -> Future {
return async(other._always)
return async { other.always($0) }
}
}
4 changes: 1 addition & 3 deletions Sources/Promise/Future/Future+Foundation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ public extension Future {
}

func on(_ queue: DispatchQueue) -> Future {
return changeContext { resolve in
queue.async(execute: resolve)
}
return changeContext(queue.asyncContext)
}

func asyncAfter(deadline: DispatchTime, on queue: DispatchQueue) -> Future {
Expand Down
19 changes: 2 additions & 17 deletions Sources/Promise/Future/Future.swift
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
public final class Future<Value> {
private let result: BasicFuture<Result<Value>>

init(result: BasicFuture<Result<Value>>) {
internal init(result: BasicFuture<Result<Value>>) {
self.result = result
}
}

private extension Future {
convenience init(
context: @escaping ExecutionContext,
_ process: (Promise<Value>) throws -> Void
) {
let result = BasicPromise<Result<Value>>(future: .pending)
self.init(result: result.future.changeContext(context))

let promise = Promise(result: result)
promise.do { try process(promise) }
}
}

internal extension Future {
var testableResult: Result<Value>? {
return result.testableValue
Expand Down Expand Up @@ -82,8 +69,6 @@ public extension Future {
}

func changeContext(_ context: @escaping ExecutionContext) -> Future {
return Future(context: context) { promise in
promise.observe(self)
}
return .init(result: result.changeContext(context))
}
}
2 changes: 1 addition & 1 deletion Sources/Promise/Future/Promise.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
public final class Promise<Value> {
private let result: BasicPromise<Result<Value>>

init(result: BasicPromise<Result<Value>>) {
internal init(result: BasicPromise<Result<Value>>) {
self.result = result
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Promise/Miscellaneous/ExecutionContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import Foundation

public typealias ExecutionContext = (@escaping () -> Void) -> Void

internal let defaultExecutionContext: ExecutionContext = { DispatchQueue.main.async(execute: $0) }
internal let defaultExecutionContext = DispatchQueue.main.asyncContext
4 changes: 2 additions & 2 deletions Tests/PromiseTests/PromiseRecoverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import Promise
final class PromiseRecoverTests: XCTestCase {
func testRecover() {
let future = Future<Void>.rejected(with: SimpleError()).delayed(by: 0.1)
let recovered = future.mapError { _ in () }
let recovered = future.mapError { _ in }
assertWillBeFulfilled(recovered)
}

func testRecoverInstant() {
let future = Future<Void>.rejected(with: SimpleError())
let recovered = future.mapError { _ in () }
let recovered = future.mapError { _ in }
assertWillBeFulfilled(recovered)
}

Expand Down
9 changes: 9 additions & 0 deletions Tests/PromiseTests/PromiseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,13 @@ final class PromiseTests: XCTestCase {
called = true
wait()
}

func testContextChange() {
let future = Future.fulfilled
.changeContext { _ in XCTFail() }
.changeContext { $0() }
.map {}

assertIsFulfilled(future)
}
}

0 comments on commit 5152cf8

Please sign in to comment.