 				targetedAction(self)
 				action(self)
 			case .completed:
-				targetedCompletion(self, true)
+				// copy targeted completion to call on next run loop
+				let targetedCompletionCopy = self.targetedCompletion
 				targetedCompletion = { _ in }
 				targetedAction = { _ in }
-
-				completion(self, true)
+				DispatchQueue.main.async {
+					targetedCompletionCopy(self, true)
+					self.completion(self, true)
+				}
 			case .cancelled:
-				targetedCompletion(self, false)
+				// copy targeted completion to call on next run loop
+				let targetedCompletionCopy = self.targetedCompletion
 				targetedCompletion = { _ in }
 				targetedAction = { _ in }
-
-				completion(self, false)
+				DispatchQueue.main.async {
+					targetedCompletionCopy(self, false)
+					self.completion(self, false)
+				}
 			}
 			observer?.didChangeState(animator: self)
 		}

		// setting the toValue clears any extant targetedAction
		toValue = to
		targetedAction = action
		// will cancel any in-flight completion
		targetedCompletion(self, false)
		targetedCompletion = completion
	}

	// Animation Variables

	/// The target value of the animator
	public var toValue: T {
		didSet {
			if oldValue.components != toValue.components {
				targetedCompletion(self, false)
				targetedCompletion = { _ in }
				targetedAction = { _ in }
				setNeedsUpdate()
			}


		if needsUpdate {
			needsUpdate = update(dT: dT)
			// Unlike the completion, the action can be called sync in the flow as it will
			// update `needsUpdate` before evaluating to stop execution. This is an important
			// flow as you might change something in the animator that requires an update.
			transition(to: .updated)
		}

