Does Future.init() guarantee its trailing closure is called before return?

Is there a guarantee that the trailing closure to Future.init() is run right away? And if there is, why is the closure marked escaping?

This is condensed from a more complicated example, but the question is, is this safe to do?

 var task: URLSessionUploadTask!
 let future = Future<Data, Error> { promise in
    task = self.uploadTask(with: urlRequest, fromFile: fileURL) {
        data, response, error in
        // shove result/error into the promise here
    }
}

// Q: do we know task was created before we reach this point?  Or must I
// move the following line up inside the trailing closure to ensure the
// correct order of execution?
task.resume() 

If the trailing closure to Future.init() is run before returning, then task will have been assigned to, and I can call its resume function outside the closure. If not, I must call task.resume() inside the trailing closure to Future.

Which is it? Everything I have read indicates that Futures do NOT wait for someone to subscribe, and run their trailing closure right away. But given that the API marks the trailing closure as escaping, I'm worried that there's no contractual guarantee this is true.

1 Like

Because Future is primarily designed to work with async operations that can have results "later". So the answer is no. The future is first created and the closure is stored in a property and when the promise completes than the Future continues publishing downstream.
You can have a simple .async { } block inside the initializer block which does not guarantee when it will execute.

I don't think you understand what I'm asking. If I have an async block in the initializer, the question would become "does the async block get started (not necessarily complete)" before Future's init() returns.

The promise certainly can't "complete" before the trailing closure is even started running, obviously.

My assumption is because Future must persist the Promise = (Result<Output, Failure>) -> Void which is @escaping and is the parameter to another closure, thus the other closure must also be escaping

I think it's best to assume no. Even if it's guaranteed to start before return, it could easily be suspended before the first line of the closure is even executed, or before any useful effect is taking place. The closure would technically have started, but you still have no guarantee of the execution order between two different threads/queues.

I’m asking this because I’ve seen so much discussion that people are surprised that the trailing closure gets run even if nobody ever subscribes; as such, I assumed that Future just runs the code right away, in the same queue.

And as such, the trailing closure better not be blocking, but simply launch some async operation that eventually runs a completion handler, where we fulfill the promise.

The doc does say it's invoked every time the publisher emits an element. Perhaps the real question, is whether a publisher emits elements when no one's listening.

This article asserts the closure is ever only run once:
https://www.donnywals.com/using-promises-and-futures-in-combine/

1 Like

I find that the OpenCombine library helps me understand sometimes how Publishers in Apple's Combine might be implemented.

So the init function in that file immediately runs _attemptToFulfill, without dispatching to another queue, and doesn’t bother to hang on to it. So why mark it escaping?

1 Like

You make a fair point, I am trying to understand it myself now :D

Any updates on this one? With a simple test we can see that closure is invoked synchronously. There is no need in Future to keep reference to this closure.

print("before")
_ = Future<Void, Error> { _ in
    print("inside")
}
print("after")

Output:

before
inside
after

If there is no reason for this closure to be @escaping can we expect this attribute to be removed in one of the next releases (probably also with backward compatibility since this should be possible) or am I missing something?