AwaitlessKit

9.0.0

Simplifies the migration to async/await. It likely performs better than your ad hoc hacks.
bonkey/AwaitlessKit

What's New

9.0.0

2025-09-18T12:00:41Z

What's Changed

  • Refresh Sample App and Noasync->Awaitless rename by @bonkey in #36
  • Add PromiseKit integration with @AwaitlessPromise and @awaitable macros by @Copilot in #29
  • Improve macro-generated code formatting by @Copilot in #31

Full Changelog: 8.2.0...9.0.0

Ask DeepWiki Tests codecov

AwaitlessKit

Automatically generate legacy sync interfaces for your async/await code, enabling easy migration to Swift 6 Structured Concurrency with both APIs available.

AwaitlessKit provides Swift macros to automatically generate synchronous wrappers for your async functions, making it easy to call async APIs from existing synchronous code. This helps you adopt async/await without breaking existing APIs or rewriting entire call chains at once.

Table of Contents

Quick Start

Add the @Awaitless macro to your async functions to automatically generate synchronous wrappers:

import AwaitlessKit

class DataService {
    @Awaitless
    func fetchUser(id: String) async throws -> User {
        // Your async implementation
    }
    // Generates: @available(*, noasync) func fetchUser(id: String) throws -> User
}

// Use both versions during migration
let service = DataService()
let user1 = try await service.fetchUser(id: "123")  // Async version
let user2 = try service.fetchUser(id: "456")        // Generated sync version

See more examples or documentation for more sophisticated cases.

Why AwaitlessKit?

The Problem: Swift's async/await adoption is an "all-or-nothing" proposition. You can't easily call async functions from sync contexts, making incremental migration painful.

The Solution: AwaitlessKit automatically generates synchronous counterparts for your async functions, allowing you to:

  • ✅ Migrate to async/await incrementally
  • ✅ Maintain backward compatibility during transitions
  • ✅ Avoid rewriting entire call chains at once
  • ✅ Keep existing APIs stable while modernizing internals

⚠️ Important: This library bypasses Swift's concurrency safety mechanisms. It is a migration tool, not a permanent solution.

Installation

Add to your Package.swift:

dependencies: [
    .package(url: "https://github.com/bonkey/AwaitlessKit.git", from: "7.1.0")
],
targets: [
    .target(
        name: "YourTarget",
        dependencies: ["AwaitlessKit"]
    )
]

Swift 6.0+ compiler required (available in Xcode 16 and above).

Core Features

@Awaitless - automatic sync function generation

Generates synchronous wrappers for async functions with built-in deprecation controls.

Output variants:

  • @Awaitless - Generates synchronous throwing functions that can be called directly from non-async contexts
  • @AwaitlessPublisher - Generates Combine AnyPublisher wrappers for reactive programming patterns
  • @AwaitlessCompletion - Generates completion-handler based functions using Result callbacks
  • @AwaitlessPromise & @Awaitable - Bidirectional PromiseKit integration (separate AwaitlessKit-PromiseKit product)

Concurrency note: Mark service classes and protocols as Sendable (and classes as final where possible) when using AwaitlessKit macros. @AwaitlessPublisher now uses a task-backed publisher (not Future), so cancelling a subscription cancels the underlying Task.

@Awaitlessable - protocol extension generation

Automatically generates sync method signatures and optional default implementations for protocols with async methods.

#awaitless() - inline async code execution

Execute async code blocks synchronously in non-async contexts.

@IsolatedSafe - generate thread-safe properties

Automatic runtime thread-safe wrappers for nonisolated(unsafe) properties with configurable synchronization strategies.

Awaitless.run() - low-level bridge

Direct function for running async code in sync contexts with fine-grained control.

Configuration System - four-level configuration hierarchy

AwaitlessKit provides a flexible configuration system with multiple levels of precedence for customizing generated code behavior.

  1. Process-Level Defaults via AwaitlessConfig.setDefaults()
  2. Type-Scoped Configuration via @AwaitlessConfig member macro
  3. Method-Level Configuration via @Awaitless parameters
  4. Built-in Defaults as fallback

More Examples

Non-async Function

class APIService {
    @Awaitless
    func fetchData() async throws -> Data {
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }
    // Generates: @available(*, noasync) func fetchData() throws -> Data
}

Combine Publisher

@AwaitlessPublisher generates a publisher backed by a dedicated cancellation-aware task publisher. This provides:

  • Correct cancellation propagation (cancelling the subscription cancels the underlying Task)
  • Memory behavior (no retained promise closure beyond execution)
  • Semantic clarity (single-shot mapping of async result to a Combine stream)
  • Clear failure typing: non-throwing async -> AnyPublisher<Output, Never>, throwing async -> AnyPublisher<Output, Error>

Throwing example:

class APIService {
    @AwaitlessPublisher
    func fetchData() async throws -> Data {
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }
    // Generates: func fetchData() -> AnyPublisher<Data, Error>
}

Non-throwing example:

class TimeService {
    @AwaitlessPublisher
    func currentTimestamp() async -> Int {
        Int(Date().timeIntervalSince1970)
    }
    // Generates: func currentTimestamp() -> AnyPublisher<Int, Never>
}

Main thread delivery:

class ProfileService {
    @AwaitlessPublisher(deliverOn: .main)
    func loadProfile(id: String) async throws -> Profile {
        // ...
    }
    // Generated publisher delivers value & completion on DispatchQueue.main
}

Under the hood the macro calls an internal factory that uses TaskThrowingPublisher / TaskPublisher (adapted from a Swift Forums discussion on correctly bridging async functions to Combine) to produce the AnyPublisher.

PromiseKit Integration

Bidirectional conversion between async/await and PromiseKit with @AwaitlessPromise and @Awaitable:

// Add PromiseKit integration to Package.swift
.product(name: "AwaitlessKit-PromiseKit", package: "AwaitlessKit")

Async to Promise conversion:

import AwaitlessKit
import PromiseKit

class NetworkService {
    @AwaitlessPromise(prefix: "promise_")
    func fetchData() async throws -> Data {
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }
    // Generates: func promise_fetchData() -> Promise<Data>
}

// Use with PromiseKit
service.promise_fetchData()
    .done { data in print("Success: \(data)") }
    .catch { error in print("Error: \(error)") }

Promise to async conversion:

class LegacyService {
    @Awaitable(prefix: "async_")
    func legacyFetchData() -> Promise<Data> {
        return URLSession.shared.dataTask(.promise, with: url).map(\.data)
    }
    // Generates: 
    // @available(*, deprecated: "PromiseKit support is deprecated; use async function instead")
    // func async_legacyFetchData() async throws -> Data
}

// Use with async/await
let data = try await service.async_legacyFetchData()

Perfect for gradual migration between PromiseKit and async/await architectures.

Completion Handler

class APIService {
    @AwaitlessCompletion
    func fetchData() async throws -> Data {
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }
    // Generates: func fetchData(completion: @escaping (Result<Data, Error>) -> Void)
}

Documentation

AwaitlessKit includes comprehensive DocC documentation with detailed guides, examples, and API reference.

📖 Complete Documentation

Key Documentation Sections

What You'll Find

  • Quick Reference - Fast lookup for common macro usage patterns and configurations
  • Real-world Examples - From simple async functions to complex migration scenarios
  • PromiseKit Integration - Complete bidirectional conversion guide with migration strategies
  • Configuration Patterns - Process-level, type-scoped, and method-level configuration examples
  • Migration Strategies - Progressive deprecation, brownfield conversion, and testing approaches
  • Best Practices - Naming conventions, error handling, and testing approaches
  • Technical Details - Macro implementation, SwiftSyntax integration, and extension points

The documentation is designed to help you successfully adopt async/await in your projects while maintaining backward compatibility during the transition period.

Migration Overview

Phase 1: Add Async Code with autogenerated sync function

Implement async functions while maintaining synchronous compatibility for existing callers:

class DataManager {
    // Autogenerate noasync version alongside new async function
    @Awaitless
    func loadData() async throws -> [String] {
        // New async implementation
    }
}

Phase 2: Deprecate generated sync function

Add deprecation warnings to encourage migration to async versions while still providing the synchronous fallback:

class DataManager {
    @Awaitless(.deprecated("Migrate to async version by Q2 2026"))
    func loadData() async throws -> [String] {
        // Implementation
    }
}

Phase 3: Make sync function unavailable

Force migration by making the synchronous version unavailable, providing clear error messages about required changes:

class DataManager {
    @Awaitless(.unavailable("Sync version removed. Use async version only"))
    func loadData() async throws -> [String] {
        // Implementation
    }
}

Phase 4: Remove macro and autogenerated function

Complete the migration by removing the AwaitlessKit macro entirely, leaving only the pure async implementation:

class DataManager {
    func loadData() async throws -> [String] {
        // Pure async implementation
    }
}

License

MIT License. See LICENSE for details.

Credits


Remember: AwaitlessKit is a migration tool, not a permanent solution. Plan your async/await adoption strategy and use this library to smooth the transition.

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

Last updated: Mon Oct 27 2025 16:47:52 GMT-0900 (Hawaii-Aleutian Daylight Time)