Returning async result

Is it possible to return the async task result from some calling method without awaiting the task or having the calling method be async itself? e.g.

func outerWrapper<S>(arg1: Any, arg2: String = "ignore me") -> AsyncTask<S> {
  return theAsyncFunc(arg1: arg1, arg2: arg2)
}

func theAsyncFunc<S>(arg1: Any, arg2: Any) async throws -> S {
 ...
}

This kind of thing is common in JS where a function can forward up the Promise returned by an async function without the overhead of awaiting it and fulfilling its own promise.

oh... then hopefully it's obvious that you'd want to write something like, await outerWrapper(...)

I’m not sure I understand your question. Your code snippet includes an AsyncTask identifier, but that’s not an Apple thing. Is that from some third-party SDK? Or perhaps it’s a proposal for what you’d like to see?

My best guess is that you want some automatic way to convert an async task into a promise. If so, that’s depends on what promise library you’re using. The only Apple one, Combine, does not support this (although it does support going the other way). However, you should be able to build a general solution that wraps any async function as single-value publisher. For example, a quick search of the ’net turned up Calling async functions within a Combine pipeline.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

@eskimo ....my bad. AsyncTask is a proposal for what I'd like to see if it didn't already exist.. It's just a placeholder for some declaration that would let the compiler know that await outerWrapper() is syntactical ok. I guess I could also write it like:

func outerWrapper<S>(arg1: Any, arg2: String = "ignore me") async throws -> S {
  return theAsyncFunc(arg1: arg1, arg2: arg2)
}

where it still just avoids using the await keyword. But, in my mind, if the async keyword is used in the function signature, it's an error not to await any asynchronous tasks it calls (which is a common mistake in JS).

I'm not really looking to convert the task at all. I'm more looking to skip an await call in a call stack when a given function, outerWrapper in this case, only needs to await once as part of (each of) its return statement(s). In this case, if we can ensure the caller of outerWrapper makes an await call, then the task from theAsyncFunc can propagated up the call stack, and outerWrapper doesn't technically do anything asynchronously in its own function body.

Another way I'm thinking about it is returning an async let:

func outerWrapper<S>(arg1: Any, arg2: String = "ignore me") -> SomeAsyncTaskThing<S> {
  async let result = theAsyncFunc(arg1: arg1, arg2: arg2)
  return result
}

But it sounds like each function in the call stack must be async and must await all the async functions it calls. I can imagine this makes managing the call stack easier for debug purposes. So I get it.

Kind of a side note, as I play with this more, it looks like I'm going to do this a lot because my SwiftUI view models currently have an inheritance hierarchy, so that they can't be actors until the whole code base is overhauled.

Task {
  do {
    let result = try await someAsyncFunc(....)
    await MainActor.run {
      self.swiftUIStateVar = result.someValue
    }
  } catch {
    await MainActor.run {
      self.errorToDisplay = error
    }
  }
}

What am I missing? Is there or can there be a way to run the Task's body on the main thread but send off the awaited call to background threads, like how it would work with asyncio in python?

Sorry, but I’m still kinda lost with regards your overall goals. One thing I can say, with reference to your most recent post, is that the concept of threads doesn’t really apply to Swift concurrency. There are tasks and actors, with the only edge case being that certain work, like UI state updates, have to be done on a specific actor.

Anyway, let’s start with some basics. Why are you not doing everything on the main actor? That’s my default position in all the code I write (in a pre Swift concurrency world it’s doing everything on the main thread). There are good reasons to move off the main actor — typically because you have something that’s either CPU or I/O bound [1] — but my default position is to do everything on the main actor and then move very specific stuff to secondary actors if they warrant it.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Oh, and I mean synchronous I/O bound here, like walking a large file system hierarchy. If the underlying I/O abstraction supports async operations, like URLSession, it’s often fine to do all the bookkeeping on the main actor.

For the first part of this thread, the goal was just more efficient code. The assumption is that this:

func outerWrapper<S>(arg1: Any, arg2: String = "ignore me") throws -> AsyncTask<S> {
  return theAsyncFunc(arg1: arg1, arg2: arg2)
}

is more efficient than this:

func outerWrapper<S>(arg1: Any, arg2: String = "ignore me") async throws -> S {
  return await theAsyncFunc(arg1: arg1, arg2: arg2)
}

Negligibly so, sure. But I actually care about the efficiency of my code. It also makes for fewer edits in the migration to concurrency. If the former is not allowed in swift, so be it. Not a big deal. Was just curious and wanted to advocate.

For the second side note, the overall Task is run in a method of an ObservableObject, which serves as one of my view models and which is triggered by some user interaction with SwiftUI. The added complication is that the class inherits some functionality from NSObject and conforms to a protocol that is common to most of the models in my project. So:

class MyViewModel: NSObject, LoadingProtocol, SomeObjcDelegate {
  @Published var loading: Bool = false
   .....

  func businessLogic() {
    loading = true
    Task {
      do {
        let result = try await someAsyncFunc(....)
        await MainActor.run {
          self.swiftUIStateVar = result.someValue
        }
      } catch {
          await MainActor.run {
            self.errorToDisplay = error
          }
      } finally {
        await MainActor.run {
          self.loading = false
        }
      }
    }
  }
}

And I agree that doing everything in the main actor is ideal, which is what I kind of assumed would be the case. And, if I'm not mistaken, I would be able to add the @MainActor modifier to the class definition to make it so... except for the inheritance. And the modifier prevented the class from conforming to the protocol.

The await someAsyncFunc(....) is ultimately async because it makes a URLSession dataTask call. At run time, when it returned, I got a purple warning/error about the code not running on the main actor, and the UI didn't update. As written above, it seems to work. But I posted just because I'm still new to the api and maybe there's a better way given the constraints of my situation.

And my critique, then, with my limited perspective, is that it should be main actor by default, and the declaration I would like to add would mark whatever can be pushed off to other actors: something like await method() on someClassOfActor. Arguably, each method in the call stack from someAsyncFunc down to just before the actual dataTask is quick enough to run on the main actor. And every step processing the returned data back up the call stack is also probably quick enough. So that, at least in my case, main actor by default without extra decorators would be sufficient.

PromiseKit, the package from which I'm migrating, essentially operates this way, dispatching each callback on the main queue by default unless instructed otherwise.

Returning async result
 
 
Q