{"id":17691214,"url":"https://github.com/chipjarred/async","last_synced_at":"2025-08-13T02:45:41.213Z","repository":{"id":63906885,"uuid":"263849145","full_name":"chipjarred/Async","owner":"chipjarred","description":"Swift Async package that makes creating and using asynchronous tasks easier.","archived":false,"fork":false,"pushed_at":"2021-03-02T14:27:40.000Z","size":141,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-06T13:52:16.957Z","etag":null,"topics":["async","asynchronous","completion-handler","concurrency","concurrent-programming","dispatchqueue","future","gcd","grand-central-dispatch","multithreading","mutex","promise","swift-framework","swift-package-manager","thread","thread-safety"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chipjarred.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-14T07:45:10.000Z","updated_at":"2024-12-09T14:04:51.000Z","dependencies_parsed_at":"2022-11-28T22:45:25.419Z","dependency_job_id":null,"html_url":"https://github.com/chipjarred/Async","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/chipjarred/Async","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chipjarred%2FAsync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chipjarred%2FAsync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chipjarred%2FAsync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chipjarred%2FAsync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chipjarred","download_url":"https://codeload.github.com/chipjarred/Async/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chipjarred%2FAsync/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270170224,"owners_count":24539354,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-13T02:00:09.904Z","response_time":66,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","asynchronous","completion-handler","concurrency","concurrent-programming","dispatchqueue","future","gcd","grand-central-dispatch","multithreading","mutex","promise","swift-framework","swift-package-manager","thread","thread-safety"],"created_at":"2024-10-24T12:07:54.694Z","updated_at":"2025-08-13T02:45:41.144Z","avatar_url":"https://github.com/chipjarred.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"#  Async\n\n`Async` is a free-to-use framework of functions and types that I have found incredibly useful in working with *Grand Central Dispatch*, although the work horses of the library, `Future` and its coresponding `Promise`,  are useful in a wider variety of contexts.   I've been using these in my own code for a long time, and have always meant to share them, so now I finally am.   I have other types and functions I have found useful that I may add later, but these form the core functionality I use nearly every time I use GCD. \n\nEven though `Future` is really the central feature, the library is called `Async` because it provides global `async` free functions and adds coresponding methods on `DispatchQueue` that return a `Future`, so I almost never need to explictly create a `Promise` , and often the `Future` itself just disappears behind fluid completion handler syntax, so that it almost seems as though the library is about `async`.   But actually `Future` is the hero, and it's more flexible than most `Future` implementations I've seen.\n\nI often try an idea by using writing a command line tool rather than an actual AppKit/UIKit application, so this package is specifically designed to be independent of those.  It uses only GCD and Foundation.\n\n## What's Included\n\n### async\n\nThere are a  few variations of a global  `async` free function that immediately return a `Future`, asynchronously executing your closure, which can safely throw an error, on a global default concurrent `DispatchQueue`. You don't need to create a queue explictly yourself, unless you just need it for some other reason.  \n\nI provide a few variations, because I find it annoying that it's necessary to specify a deadline in GCD's `asyncAfter` as a `DispatchTime`.  I almost always want my code to run either as soon as possible or after a specified delay from the moment I called `async`, and nearly never need it to run at a specific point in time.  So I provide variations on my `async` function that allow you specify a deadline as a `DispatchTime`, as with GCD's native version, or a delay as `DispatchTimeInterval` or `TimeInterval`.\n\n### sync\n\nThere are *synchronous* versons of the global  `async` free functions.  They merely run your closure immediately in the current thread as though you had called it directly yourself.  This is useful for several reasons:\n        \n• It allows an intermediate step in refactoring synchronous code into asynchronous during which you are still executing a closure synchronously while getting a `Future` from it.  In that case, the `Future` is ready immediately when your closure returns, and if you added handlers, they are called immediately.   Then by simply changing `sync` to `async`, it becomes truly asynchronous.   \n\n• It is sometimes helpful for debugging to use `sync`  instead of `async` temporarily, and it is a simple one-letter code change.   \n\n• It returns a `Future`, so you can use it inside of your own code as an easy way to return a `Future` without worrying about how to use `Promise`, although `Promise` is pretty easy to use directly.\n\n\n### DispatchQueue extension\n\n`async` and `sync` methods have been added to `DispatchQueue` to corespond to their global versions, but you get the `Future` returning behavior on the queue of your choice instead of the default global concurrent queue.\n\n### Mutex\n\n`Mutex` is a class that simplifies guarding shared data for thread safety by providing a `withLock` method to automate locking and unlocking the mutex, which is what should be used most of the time; however,  recognizing that sometimes it's necesary to interleave locking and unlocking different mutexes in ways that aren't always conveniently nestable, it also provides explicit `lock`, `unlock` methods as well as a failable `tryLock`  method that allows you to specify a time-out.\n\n### Future\n\nThis implementation allows you use the typical fluid style you see in most libraries, but it also allows you to use the `Future` as a genuine place-holder for a value, similar to C++'s `std::future`, that you can store away and query when you need it.  It also allows you to specify a timeout for both usages.\n\n### Promise\n\n`Promise` is the necessary but usually hidden counterpart of `Future`.   Even though its interface is really simple, if you mainly just need to get a `Future` from  `async`, you'll never need to create a `Promise` yourself.  On the other hand, you can use `Promise` to return a `Future` from any code you like, for example your own wrapper around a third party asynchronous library.\n\n\n## Basic Usage\n\nAlthough each function  and type is fully documented with markup comments, so in Xcode you can easily get a reference with QuickLook, it is helpful to see at least basic examples as a starting point.  I'll present these in the order I think most people will use them.\n\n### Future and async\n\n`Future` doesn't make a lot of sense in isolation, so I'll describe it in conjuntion with `async`, but understand that fundamentally the only way to obtain a `Future` directly is from a `Promise`, which will be described later.  `async` uses `Promise` internally to return a `Future`.  There's nothing special about this, and you can do that yourself to use `Future` in other contexts.\n\nYou can think of a `Future` in several ways, and they are all simultaneously true:\n\n- It is the receiver side of a one-shot, one-way communicaton channel for the result of a closure.  `Promise` is the sender end of that channel.\n- It is a placeholder for some result to be set by some other code in the future, possibly in another thread of execution.\n- It is a means of specifying completion handlers to be executed when a closure returns or throws, possibly in another thread of execution.\n\nAttaching handlers (callbacks) to a `Future` is the easiest and safest way to keep your code from blocking on a result, since you can just continue on doing something else after attaching them, and your handlers will be called automatically when your asynchronous code completes.  In terms of ease of use and safety, handlers are normally the way to go.  \n\nThey do have some drawbacks though.  If you're combining the results of several asynchronous tasks that must all complete before progress can be made, handlers can be awkward.  For example, in a computation graph, all input computations must complete before the current node can be evaluated.   In that case the current computation should block until the inputs are ready.  Of course, you could force the square peg of handlers into this round hole, but that's not a good use case for handlers.  You'd need to update some counter for inputs, ensure that updates to that counter from multiple handlers happen atomicly, and block on that counter.  It's not that hard, but what a needless, inefficient and error-prone mess!  By comparison, it's ideal for using collection of `Future`s as placeholders.  Just iteratively wait on each of them.  When the loop completes they're all ready, and you can proceed to evaluate the current node.  This is exactly why I provide that alternative.\n\nOn the other hand, fetching data from a network or responding to events, very common cases, are usually ideal uses of handlers, and keeping a `Future` for a placeholder becomes the awkward one, because it requires you either to block until it's ready, or to write some kind of loop or other scheme to continually check when it's ready while your code continues.\n\nHaving said that completion handlers are a good fit for callbacks, such as for HTTP requests, using them as placeholders is a good way to avoid deeply nested callbacks when the results of one request require spawning another request, and another. \n\nThe implementation of `async` in this package, differs from GCD's native `async` and `asyncAfter` methods in two ways.  The first is that it returns a `Future`, and the second is that there are global free function variants that use a default concurrent `DispatchQueue`, in addition to methods on `DispatchQueue` itself.\n\nWhen you call `async`, it schedules your closure for execution, using GCD's native `async`, but it also immediately returns a `Future` for the value your closure will return, or the error it will throw.  You can hold on to this `Future` as a means for querying for your closure's eventual result, or you can use it to attach handlers... or both.  The two ways of using it can be used together, if that makes sense for your application.\n\n#### `Future` handler attachment\n\n##### `.onSuccess` and `.onFailure`\nAs a basic example, let's say we have a long-running function, `foo() throws -\u003e Int`.  We can schedule `foo` with `async`, and attach handlers to the returned `Future` like so:\n\n```swift\nasync { return try foo() }.onSuccess {\n    print(\"foo returned \\($0)\")\n}.onFailure {\n    print(\"foo threw exception, \\($0.localizedDescription)\")\n}\n```\n\n`foo` will run concurrently, and if it eventually returns a value, the closure passed to `.onSuccess` will be called with that value.   If on the other hand, `foo` throws, the closure passed to `.onFailure` is called with the error.\n\nNotice how the `Future` doesn't explicitly appear in the above code, but it's there.  It's what `async` returns, in this case, `Future\u003cInt\u003e`, and it's that `Future`'s `.onSuccess` method that we're calling to specify the success handler.  `.onSuccess` returns the same `Future`, which allows us to chain a `.onFailure` method call to schedule our failure handler.    It is equivalent to:\n\n```swift\nlet future = async { return try foo() }\n\nfuture.onSuccess {\n    print(\"foo returned \\($0)\")\n}\n\nfuture.onFailure {\n    print(\"foo threw exception, \\($0.localizedDescription)\")\n}\n```\n\n\nYou can attach handlers in any order, if you prefer to put the failure handler first.\n\n##### `.onCompletion`\nIf you prefer to use Swift's `Result` type, you can use a more general `.onCompletion` handler:\n\n```swift\nasync { return try foo() }.onCompletion {\n    switch $0 {\n        case let .success(value): \n            print(\"foo returned \\(value)\")\n        case let .failure(error): \n            print(\"foo threw exception, \\(error.localizedDescription)\")\n    }\n}\n```\n\nYou can specify as many handlers as you like, mixing and matching, completion handlers, success handlers, and failure handlers.  All applicable handlers will be called concurrently.  This allows you to return the future you got from `async` so that code further up the call chain can attach their own handlers.\n\n\n##### `.timeout`\nYou can also specify a time-out for the `Future` using the same fluid style.  If the specified time-out elapses before the closure completes, an error is set in the `Future` and its `.onFailure` and `.onCompletion` handlers are called.  See the comment documentation for `Future` for more information on that.\n\n\n#### `Future` as a placeholder\n\nAs an alternative to the fluid, functional-like, usage above, you can use `Future` in a more traditionally imperative way, as a placeholder for a yet to be determined value.   Used this way, it's much more like C++'s `std::future`.   This is especially useful when you use `async` to subdivide a larger task into to a number of concurrent subtasks, which must be combined into a final result before continuing.\n\nWhen using `Future` as a placeholder, you store it away as you might store the actual value returned by the asynchronous code or the error thrown by it, if it had been called synchronously, and then query the `Future` for the value or error some time later when you need it.  To support this, `Future` provides blocking properties and methods to query the future and wait for it to be ready, as well as a non-blocking property to query its ready state.\n\nAny handlers that have been attached will still be run, whether or not you use `Future` as a placeholder.    The two styles of use can be used together.\n\n##### Blocking methods and properties\n\nBe aware that in an AppKit/UIKit application, using blocking methods and properties in the main thread can make your app unresponsive while they block.   Either use them in separate thread that can safely block, use `.isReady` to do something else when the `Future` is not ready, ensure that all asynchronous calls will complete quickly, or just avoid blocking methods and properties altogether by attaching handlers instead. \n\n###### `.value` and `.error`:\nYou can obtain the value or error from the future with its `.value` and `.error` properties.  We'll use the same `foo` from the previous examples:\n\n```swift\nlet future = async { return try foo() }\n\n// future.value and future.error will block until the foo returns or throws\nif let value = future.value {\n    print(\"foo returned \\(value)\")\n}\nelse if let error = future.error {\n    print(\"foo threw exception, \\(error.localizedDescription)\")\n}\n```\n\nThese properties *only* return when the future is ready, meaning that `foo` has either returned a value or thrown an error.  Until then, they just block, waiting for `foo` to complete.   When `foo` does complete, one of the following will be true:\n\n- If it returns a value, `.value` will contain that value, and `.error` will be `nil`.   \n- If it throws an error, `.value` will be `nil`, and `.error` will contain the error.  \n\nThe `Future` will never have both an error and a value.\n\nNote that if a `timeout` modifer was set as mentioned above, and the specified time-out elapses before the closure completes, `.error` will contain `FutureError.timedOut`.  \n\n\n###### `.result`\nIf you prefer to use Swift's `Result` type, you can access the `.result` property instead of `.value` and `.error`:\n\n```swift\nlet future = async { return try foo() }\n\n// future.result will block until future is ready\nswitch future.result {\n    case let .success(value): \n        print(\"foo returned \\(value)\")\n    case let .failure(error): \n        print(\"foo threw exception, \\(error.localizedDescription)\")\n}\n```\n\n`.result` will block in exactly the same way as `.value` and `.error`.\n\nIf a `timeout` modifer was set as mentioned above, and the specified time-out elapses before the closure completes, `.result` will be `.failure(FutureError.timedOut)`.  \n\n###### `.getValue()`\nIf you prefer to use `do {...} catch {...}` blocks for error handling, `Future` provides a throwing `.getValue()` method:\n\n```swift\nlet future = async { return try foo() }\n\n// future.getValue() will block until the foo returns or throws\ndo \n{\n    let value = try future.getValue()\n    print(\"foo returned \\(value)\")\n}\ncatch { print(\"foo threw exception, \\(error.localizedDescription)\") }\n```\n\n###### `.wait()`\nIf you don't need the actual result (perhaps your closure returns `Void`), you can simply call the `.wait()` method\n\n```swift\nlet future = async { let _ = try foo() }\n\nfuture.wait() // block until future is ready\n\n// Now the future is ready, so do other stuff\n```\n    \n`.wait()` also has a time-out variant.  It is different from the `timeout` modifier mentioned above in that it does not set an error in the `Future` when it times out.  It merely stops waiting for the `Future` to be ready, throwing an error itself when it times out.  Refer to `Future`'s comment documentation for more information.\n\n\n##### Non-blocking methods and properties\n###### `.isReady`\nThe blocking behavior of `.wait()`,  `.value` ,  `.error` and `.result` is useful if the code following them depends on the value returned or error thrown by `foo`, but it can be a problem if you could do other work while you wait for the `Future` to be ready, because it stops your current thread in its tracks until `foo` is done, in which case, you don't get much value from using `async`.  For this reason, the ability to determine if the `Future` is ready without blocking is essential.  You can do this with the `.isReady` property:\n\n```swift\nlet future = async { return try foo() }\n\nwhile !future.isReady {\n    // Do some other work while we wait\n}\n\nif let value = future.value {\n    print(\"foo returned \\(value)\")\n}\nelse if let error = future.error {\n    print(\"foo threw exception, \\(error.localizedDescription)\")\n}\n```\n\n*If you're not going to do other work while you wait for the `Future` to be ready, it's far more efficient to call `.wait()` rather than looping on `.isReady`, because all of `Future's` blocking methods and properties, including `.wait()`, use `DispatchSemaphore` under the hood, which can truly suspend the thread, whereas spinning on `.isReady` will consume CPU cycles unnecessarily.*\n\n#### `async` variations\n\nAll of the `async` variants allow you to specify quality of service (qos), and flags just as the GCD native `async` and `asyncAfter` do, and use the same default values when you don't specify them.\n\nThe following examples use the global async free functions, but the `DispatchQueue` extension provides equivalent instance methods with the same signatures as the global functions, so you can call them on a specific `DispatchQueue`.\n\n##### `.async()`\nIf you want your closure to be executed as soon as possible, you can call it as the previous examples with no deadline or delay interval.\n\n```swift\nlet future = async { return try foo() }\n```\n\n##### `.async(afterDeadline:)`\nIf you need to delay execution of your closure until a specific point in time, you can use the `afterDeadline` variant to specify a `DispatchTime` or `Date`\n\n```swift\nlet deadline: DispatchTime = .now() + .milliseconds(1000)\n\n// Some time later...\nlet future = async(afterDeadline: deadline) {\n    return try foo()\n}\n```\n    \n##### `.async(afterInterval:)`\nTo specify a delay interval using just `DispatchTimeInterval`, you can use the `afterInterval` variant\n\n```swift\nlet future = async(afterInterval: .milliseconds(1000)) {\n    return try foo()\n}\n```\n\n##### `.async(afterSeconds:)`\nAlternatively, you can specify the delay as a `TimeInterval` in seconds, using the `afterSeconds` variant\n\n```swift\nlet future = async(afterSeconds: 1) { return try foo() }\n```\n\n### `sync`\n\n`sync` is the synchronous dual of `async`.  It runs the closure passed to it in the current thread before returning.\n\nIf no deadline or delay is specified, it will execute the closure immediately.  If a deadline or delay is specified, it will block until the deadline, or delay has elapsed, and then execute the closure.  Because `sync` doesn't return until the closure has been executed, any handlers attached to the `Future` it returns will be executed as soon as they are attached, if they apply.\n\nOtherwise everything said for `async` applies to `sync`.\n\n### `Mutex`\n\nOne unfortunate side effect of concurrent code, such as that executed by `async`, is that any mutable shared data that could be accessed by multiple tasks simultaneously must be guarded to avoid data races.  That's what `Mutex` is for.   You create a `Mutex` to guard some data, and then lock it before accessing the shared data, and unlock it afterwards.   Explicitly having to lock and unlock the `Mutex` is error prone, *so the preferred way to use this implementation of `Mutex` is through its `withLock` method*.   As an example, here's a simple implemenation of a `SharedStack`:\n\n```swift\nclass SharedStack\u003cT\u003e\n{\n    private var data = [T]()\n    private var mutex = Mutex()\n    \n    public var isEmpty: Bool {\n        return mutex.withLock { data.isEmpty }\n    }\n    \n    public init() {}\n    \n    public func push(_ value: T) {\n        mutex.withLock { data.append(value) }\n    }\n    \n    public func pop() -\u003e T? {\n        return mutex.withLock { \n            return data.isEmpty ? nil : data.removeLast() \n        }\n    }\n}\n```\n\n`withLock` blocks until the a lock can be obtained, and once obtained, it executes the closure passed to it, and unlocks before returning.  The unlock happens immediately after the closure completes, regardless of whether it returns or throws.\n\n`Mutex` also provides a failable `withAttemptedLock` method that allows you specify a time-out, possibly none, after which it will stop waiting for a lock and throws `MutexError.lockFailed`.  If it fails, the lock is not obtained and the closure is not run.\n\nAlthough explicitly locking and unlocking the `Mutex` is error-prone, there are circumstances when neither `withLock` nor `withAttemptedLock` will do the job, such as interleaving locking and unlocking multiple mutexes in ways that are neither exclusive to one another, nor cleanly nestable.  For those cases, `Mutex` also provides `lock()`, `tryLock()`, and `unlock()` methods.  *Prefer `withLock` and `withAttemptedLock`* when you can use them. **If you must explicitly lock and unlock the mutex yourself, it is your responsibility to ensure that each `lock()` or successful `tryLock()` is balanced by exactly one `unlock()`**, otherwise, you'll deadlock, or crash when the `Mutex` is deinitialized.  This crashing behavior is one that is inherited by its being implemented in terms of `DispatchSemaphore` which crashes when deinitialized with a negative value, which it will have if the `Mutex` is still locked.  This is actually a good thing because it tells you unambiguously that you have a bug.  To paraphrase Apple's documentation on the subject: Don't do that!\n\nRefer to comment documentation for more information on these other methods.\n\n### `Promise`\n\n`Promise` is the sender of the `Promise`/`Future` team.  It's how you obtain a `Future` to return from your own code, and how you set the value in the `Future` from code that may be executed far removed from the code receiving the `Future`, possibly in a completely different thread.\n\n##### `.set(from:)`\nIf you wish to return a `Future` in your own custom code, you do so by creating a `Promise` and returning its `.future` property in the immediate context, while passing the closure that returns the value, possibly throwing an error,  to the `.set(from:)` method in the dispatched context.\n\nAs an example, let's suppose you want to wrap `URLSession`'s `.dataTask` to return a `Future` to the resulting `Data`.\n\n```swift\nextension URLSession\n{\n    func dataFuture(with url: URL) -\u003e Future\u003cData\u003e\n    {\n        let promise = Promise\u003cData\u003e()\n        \n        let task = self.dataTask(with: url) {\n            (data, response, error) in\n        \n            // ignoring response for this example\n            promise.set {\n                if let error = error {\n                    throw error\n                }\n                return data ?? Data()\n            }\n        }\n        task.resume()\n        return promise.future\n    }\n}\n```\n\nThe `.set(from:)` method will set the `Future` according to whether the closure returns or throws, which in this case depends on whether `dataTask` calls its completion handler with an error:\n\n- If `.dataTask` calls its completion handler with a non-`nil` error, the closure passed to `.set(from:`) will throw, causing the `Promise` to set the `Future`'s `.error`.\n- If `.dataTask` calls its completion handler with a `nil` error, the closure passed to `.set(from:)` will return `data` causing the `Promise` to set the `Future`'s `value` to `data`.\n\nIn this example, we ignore `response`, and since we return a `Future` instead of a `URLSessionDataTask`, so we also resume the task returned from `dataTask` before returning.\n\n##### `.setResult(from:)`\n`Promise` also provides a `setResult(from:)` method that takes a *non-throwing* closure that returns a Swift `Result`: \n\n```swift\nextension URLSession\n{\n    func dataFuture(with url: URL) -\u003e Future\u003cData\u003e\n    {\n        let promise = Promise\u003cData\u003e()\n        \n        let task = self.dataTask(with: url) {\n            (data, response, error) in\n        \n            // ignoring response for this example\n            promise.setResult { () -\u003e Result\u003cData, Error\u003e in\n                if let error = error {\n                    return .failure(error)\n                }\n                return .success(data ?? Data())\n            }\n        }\n        task.resume()\n        return promise.future\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchipjarred%2Fasync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchipjarred%2Fasync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchipjarred%2Fasync/lists"}