mxcl/PromiseKit
Promises for Swift & ObjC.
Healthy across the board
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 3mo ago
- ✓12 active contributors
- ✓Distributed ownership (top contributor 43% of recent commits)
Show 3 more →Show less
- ✓MIT licensed
- ✓CI configured
- ✓Tests present
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/mxcl/promisekit)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/mxcl/promisekit on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: mxcl/PromiseKit
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/mxcl/PromiseKit shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across the board
- Last commit 3mo ago
- 12 active contributors
- Distributed ownership (top contributor 43% of recent commits)
- MIT licensed
- CI configured
- Tests present
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live mxcl/PromiseKit
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/mxcl/PromiseKit.
What it runs against: a local clone of mxcl/PromiseKit — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in mxcl/PromiseKit | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 107 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of mxcl/PromiseKit. If you don't
# have one yet, run these first:
#
# git clone https://github.com/mxcl/PromiseKit.git
# cd PromiseKit
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of mxcl/PromiseKit and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "mxcl/PromiseKit(\\.git)?\\b" \\
&& ok "origin remote is mxcl/PromiseKit" \\
|| miss "origin remote is not mxcl/PromiseKit (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "Sources/Promise.swift" \\
&& ok "Sources/Promise.swift" \\
|| miss "missing critical file: Sources/Promise.swift"
test -f "Sources/Resolver.swift" \\
&& ok "Sources/Resolver.swift" \\
|| miss "missing critical file: Sources/Resolver.swift"
test -f "Sources/Thenable.swift" \\
&& ok "Sources/Thenable.swift" \\
|| miss "missing critical file: Sources/Thenable.swift"
test -f "Sources/AnyPromise.swift" \\
&& ok "Sources/AnyPromise.swift" \\
|| miss "missing critical file: Sources/AnyPromise.swift"
test -f "Sources/AnyPromise.m" \\
&& ok "Sources/AnyPromise.m" \\
|| miss "missing critical file: Sources/AnyPromise.m"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 107 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~77d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/mxcl/PromiseKit"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
PromiseKit is a production-grade promise/futures library for Swift and Objective-C that abstracts asynchronous operations into chainable, composable objects. It provides .done(), .catch(), .ensure(), and .then() handlers that eliminate callback hell and make async code read sequentially. It has specialized extensions for iOS/macOS/tvOS/watchOS APIs (URLSession, CLLocationManager, etc.) and bridges seamlessly between Swift and Objective-C. Modular framework structure: Sources/ contains core promise types (Promise.swift, AnyPromise.swift for ObjC bridging, Async.swift for async/await prep), with Extensions/ (implied) holding platform-specific integrations (URLSession+Promise, CLLocationManager+Promise, etc.). Documentation/ has guides (GettingStarted.md, CommonPatterns.md) and examples (detweet.swift, ImageCache.md). Xcode project manages iOS/macOS/tvOS/watchOS targets via PromiseKit.xcodeproj. JavaScript polyfill tests in root indicate promises-aplus spec compliance.
👥Who it's for
iOS/macOS/tvOS/watchOS developers who need to manage asynchronous operations (network requests, location services, background tasks) without nested callbacks. Used by top-100 CocoaPods apps and teams integrating Objective-C legacy code with Swift. Also valuable for developers considering migration to Swift 5.5+ async/await via the Async+ companion library.
🌱Maturity & risk
Highly mature and production-ready. PromiseKit is a top-100 CocoaPod used in many popular apps worldwide, with comprehensive test coverage (see codecov badges in README), active CI/CD via GitHub Actions (.github/workflows/ci.yml, cd.yml), and professional support available through TideLift. Version 8 is current and actively maintained, supporting Xcode 13+. The codebase shows solid release discipline with multiple Swift/Xcode version variants (Package@swift-4.2.swift, Package@swift-5.3.swift).
Low risk for new features, but moderate risk for major architectural changes: the repo is feature-complete and single-maintainer (mxcl), meaning velocity depends on one person's availability. PromiseKit 8 dropped some Podspecs for older Xcodes, signaling breaking changes are possible when Swift/Xcode versions sunset. Dependency surface is minimal (primarily Swift stdlib and platform frameworks), reducing supply-chain risk. The core Objective-C bridge (Sources/AnyPromise.m, AnyPromise.swift) is complex and tightly coupled to runtime behavior, making refactoring risky.
Active areas of work
Project is in maintenance mode. Recent activity focuses on: (1) CI/CD stability across Xcode 13+ via GitHub Actions (workflows/ci.yml, cd.yml), (2) dropping legacy Xcode 8-10 support in v8, (3) preparing migration guidance toward Swift 5.5+ async/await (Async+ link in README). No evidence of major feature work, suggesting the maintainer is stabilizing the library for long-term use rather than adding new APIs.
🚀Get running
git clone https://github.com/mxcl/PromiseKit.git
cd PromiseKit
open PromiseKit.xcodeproj
Then select the PromiseKit scheme and build (⌘B). For Swift Package Manager: swift build. The playground (PromiseKit.playground/Contents.swift) provides interactive examples.
Daily commands:
Xcode: Open PromiseKit.xcodeproj, select PromiseKit scheme, press ⌘R or ⌘B. SwiftPM: swift build or swift test in repo root. CocoaPods (for dependents): Add pod "PromiseKit", "~> 8" to Podfile, run pod install. Playground: Open PromiseKit.playground/Contents.swift in Xcode and run (▶ button top-left).
🗺️Map of the codebase
Sources/Promise.swift— Core Promise type definition and primary API surface; all async chains flow through this abstraction.Sources/Resolver.swift— Resolver pattern implementation that settles promises; essential for understanding promise lifecycle and state transitions.Sources/Thenable.swift— Protocol defining chainable promise operations (then, done, catch); foundational abstraction for all continuations.Sources/AnyPromise.swift— Swift wrapper over Objective-C AnyPromise; bridges Swift and ObjC promise ecosystems and handles type erasure.Sources/AnyPromise.m— Core Objective-C promise implementation; handles settlement, callback dispatch, and thread-safety for the entire library.Sources/when.swift— Combinator for waiting on multiple promises; implements parallel composition logic critical to complex async workflows.Sources/firstly.swift— Entry point for promise chains; sets up initial context and demonstrates the fluent DSL pattern used throughout.
🛠️How to make changes
Add Promise Support for a New Framework
- Create extension file in Extensions directory (e.g., Extensions/CoreLocation+Promise.swift) adopting standard extension naming (
Extensions) - Define extension on the framework's class with promise-returning methods; return Promise<T> or Guarantee<T> wrapping async callback APIs (
Sources/Promise.swift) - Use Resolver to convert callback-based API to promise:
Promise<T> { resolver in /* call framework method with callback that calls resolver.fulfill/reject */ }(Sources/Resolver.swift) - Add tests in Tests/Bridging validating the extension works in both Swift and Objective-C contexts with proper error propagation
Implement Error Recovery in a Promise Chain
- Chain .catch() or .recover() using Catchable protocol to intercept errors; see Error.swift for custom error types (
Sources/Catchable.swift) - Use typed error patterns to match specific errors:
catch(MyError.self) { error in /* recovery logic */ }for granular error handling (Sources/Error.swift) - Return a new promise from recovery block to resume chain, or throw to propagate; recovery must be type-compatible with original promise value (
Sources/Promise.swift)
Compose Multiple Async Operations in Parallel
- Use when(fulfilled:) combinator to wait for multiple promises with different value types; returns tuple of all results (
Sources/when.swift) - Initiate all async operations first (before when call) to allow true parallelism; operations execute concurrently across queues (
Sources/Async.swift) - Chain .done() on when result to process tuple of settled values; any rejection propagates immediately, canceling unresolved operations (
Sources/Thenable.swift)
Add Timeout or Delay Behavior
- Use after(seconds:) to create delayed promise; chains naturally with race() to implement timeout patterns (
Sources/after.swift) - Compose with race(promise1, promise2) where one is your operation and other is timeout delay; returns first settled result (
Sources/race.swift) - Configure global timeout policy via Configuration.promiseTimeout if needed for application-wide defaults (
Sources/Configuration.swift)
🔧Why these technologies
- Swift generics with Promise<T> — Type-safe promise values; compiler enforces correct chaining and prevents type mismatches in async chains
- Objective-C AnyPromise + Swift interop — Enables gradual migration and coexistence of Swift and ObjC codebases; type erasure bridges language gap
- Grand Central Dispatch (GCD) integration — Leverages OS-level thread pooling and queue management; avoids custom thread overhead and respects system concurrency limits
- A+ Promise specification compliance — Standardized promise semantics ensure predictable behavior and reduce learning curve for developers familiar with JS promises
⚖️Trade-offs already made
-
Type erasure via AnyPromise for cross-language bridging
- Why: Swift generics cannot cross into ObjC; AnyPromise loses compile-time type information
- Consequence: ObjC code must use runtime type checking; bridged promises less type-safe than pure Swift chains but enables hybrid codebases
-
Callback-based resolution model (Resolver) rather than async/await syntax
- Why: Library predates Swift async/await; supports iOS 11+; closure-based callbacks work across all deployment targets
- Consequence: Steeper learning curve than modern async/await but maintains compatibility with older iOS versions
-
Separate Guarantee<T> type for infallible operations
- Why: Distinguishes operations that cannot error from those that can; clarifies intent and prevents unnecessary catch handlers
- Consequence: More types to learn but eliminates Optional<Error> confusion and improves code clarity
-
undefined
- Why: undefined
- Consequence: undefined
🪤Traps & gotchas
- ObjC/Swift bridging complexity:
AnyPromise.muses Objective-C runtime introspection and KVO for state tracking; modifying promise state machine risks silent failures across the bridge. 2. Thread safety assumptions: Box.swift's sealed state is thread-safe, but custom promise chains may deadlock if handlers block on dispatch queues (see Troubleshooting.md). 3. Swift version fragmentation: Multiple Package@swift-*.swift files exist; CocoaPods selects by Xcode version, but SwiftPM requires explicit versioning. 4. Extension naming convention: Platform extensions (URLSession+Promise) rely on@discardableResultand customCompactMap-like operators; adding new chains requires understanding the operator precedence and return-type erasure pattern. 5. Playground limitations: PromiseKit.playground may timeout or fail to resolve promises in interactive mode due to runloop constraints; use@testable importin unit tests instead.
🏗️Architecture
💡Concepts to learn
- Promise/Future monad — PromiseKit is built on promise monad semantics; understanding monadic composition (.then(), .flatMap()) is critical to avoiding accidentally nested promises or type mismatches
- Sealed state machine — PromiseKit's Box.swift enforces promise immutability via sealed states (pending → fulfilled/rejected); this prevents race conditions and is central to thread-safety guarantees
- Objective-C runtime bridging — AnyPromise.m uses KVO and Objective-C introspection to bridge Swift promises to ObjC; understanding objc_msgSend() and class_conformsToProtocol() is needed to debug integration issues
- Promises/A+ specification — PromiseKit's test suite includes promises-aplus-tests (in package.json); the library adheres to this spec for interoperability, so understanding chaining/rejection semantics is key
- Thread-safe sealed containers — Box.swift wraps promise state in a sealed, lock-free container; learning how to avoid deadlocks in handler callbacks is essential for production stability
- Discardable result pattern — PromiseKit chains use @discardableResult to allow fire-and-forget promises; understanding when to suppress compiler warnings and why (vs. forcing unwrap) is a common source of bugs
- Swift generic type erasure — Promise<T> chains maintain type safety but some operations (like mixed-type
.when()) require type erasure; Box and AnyPromise patterns show how to erase generics safely
🔗Related repos
apple/swift-nio— Low-level async I/O framework that provides EventLoopPromise; PromiseKit sits at a higher abstraction level over NIO-style futuresReactiveX/RxSwift— Alternative reactive streams library for Swift; competes with PromiseKit for async composition but uses Observables instead of Promisesasync-plus/async-plus— Companion library mentioned in PromiseKit README; ports PromiseKit patterns to Swift 5.5+ async/await for migration pathmxcl/Homebrew-formulae— By same maintainer (mxcl); different problem domain but shows maintainer's infrastructure taste and Swift tooling expertiseCocoaPods/CocoaPods— Dependency ecosystem PromiseKit targets; understanding CocoaPods subspecs and podspec DSL is essential for library maintenance
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive unit tests for Combine.swift integration
Sources/Combine.swift exists but there are no dedicated test files visible in the repo structure for testing Combine framework integration. This is critical for Swift developers using modern async patterns. Adding tests would ensure compatibility across iOS 13+ and verify that PromiseKit's Combine bridge works correctly with publishers and subscribers.
- [ ] Create Tests/CombineTests.swift with test cases for promise-to-publisher conversion
- [ ] Add tests for subscriber cancellation and memory management
- [ ] Test integration with common Combine operators (map, flatMap, catch)
- [ ] Run tests against multiple Swift versions using .github/workflows/ci.yml
Add Swift Package Manager (SPM) integration tests in CI pipeline
The repo has Package.swift files for multiple Swift versions (4.2, 5.0, 5.3) but the .github/workflows/ci.yml likely only tests Cocoapods (ci-podspec.yml exists). SPM is now the standard for Swift dependency management and needs dedicated CI validation to catch breaking changes early.
- [ ] Create .github/workflows/ci-spm.yml for SPM-specific testing
- [ ] Test builds with 'swift build' across macOS, iOS, and Linux environments
- [ ] Validate Package.swift manifests resolve correctly without pod dependencies
- [ ] Test SPM binary target support if applicable
Add missing Objective-C interoperability tests for AnyPromise
Sources/AnyPromise.h, AnyPromise.m, and AnyPromise.swift exist to bridge ObjC/Swift, but there's no visible ObjC test suite. Given the file AnyPromise+Private.h and the Documentation/ObjectiveC.md section, contributors need tests proving the bridge works correctly. This ensures backward compatibility for legacy ObjC codebases.
- [ ] Create Tests/ObjectiveC/ directory with .m test files using XCTest
- [ ] Test AnyPromise initialization from ObjC and conversion back to Swift promises
- [ ] Verify error handling across the ObjC/Swift boundary
- [ ] Add test cases for the private methods in AnyPromise+Private.h
🌿Good first issues
- Add Linux async/await documentation: Swift 5.5+ async/await is not documented for Linux targets in Documentation/. The Async.swift file exists but has no guide. Create Documentation/AsyncAwaitMigration.md showing side-by-side Promise → async/await examples for Linux users.
- Fill missing extension test coverage: Sources/ implies URLSession+Promise, CLLocationManager+Promise, etc. exist in Extensions/, but test files for these are not listed in the top 60. Add test targets for each extension (e.g., Tests/URLSessionPromiseTests.swift) with mock URL responses and success/failure paths.
- Update CI matrix for Xcode 15: .github/workflows/ci.yml likely has hardcoded Xcode versions. Xcode 15 (Swift 5.9) support is missing from the file list. Add Xcode 15 jobs and verify Promise.swift compiles with the latest concurrency warnings enabled.
⭐Top contributors
Click to expand
Top contributors
- @RomanPodymov — 43 commits
- @mxcl — 32 commits
- @invalid-email-address — 15 commits
- @ConfusedVorlon — 2 commits
- @marcprux — 1 commits
📝Recent commits
Click to expand
Recent commits
8c07349— NSPrivacyCollectedDataTypes (#1358) (RomanPodymov)2189e01— When + Parameter Packs (#1359) (RomanPodymov)2bc4439— v8.2.0 (mxcl)6e6ea08— Merge pull request #1353 from mxcl/fix-ci (mxcl)8d06bc7— fix ci (mxcl)7edbcaf— Android support (#1352) (marcprux)1df6e3d— Update ci-podspec.yml (RomanPodymov)aad0342— Update publish.yml (RomanPodymov)2b265f6— fix privacy file conflict with swifterSwift in Xcode 16 (#1350) (jiangyewen)6fcc080— PromiseKit 8.1.2 (invalid-email-address)
🔒Security observations
PromiseKit's codebase shows moderate security concerns, primarily driven by significantly outdated JavaScript dependencies in package.json. The Babel, Webpack, Mocha, and Sinon packages are 3-6+ years old and lack modern security patches. The core Swift/Objective-C Promise library implementation appears structurally sound with no obvious injection vectors or hardcoded secrets detected. The main risks are in the build and test toolchain dependencies. Swift/Objective-C source files follow reasonable security practices. Immediate action is needed to update development dependencies to current versions. The absence of a visible lock file (package-lock.json/yarn.lock) further increases supply chain risk.
- High · Outdated Babel Dependencies —
package.json - babel-core, babel-loader, babel-preset-env. The package.json contains babel-core@^6.26.0 and babel-preset-env@^1.6.1, which are significantly outdated. Babel 6 reached end-of-life and has known security vulnerabilities. These dependencies have not received security updates for years. Fix: Upgrade to Babel 7.x or later: babel-core -> @babel/core@^7.x, babel-loader@^9.x, @babel/preset-env@^7.x - High · Outdated Webpack Dependencies —
package.json - webpack, webpack-cli. webpack@^4.0.1 and webpack-cli@^2.0.9 are outdated versions with known security vulnerabilities. Webpack 4 is no longer actively maintained and lacks critical security patches. Fix: Upgrade to webpack@^5.x or @^6.x and webpack-cli@^5.x or later with corresponding security patches - Medium · Outdated Mocha Test Framework —
package.json - mocha. mocha@^5.0.1 is significantly outdated (released in 2018) and may contain unpatched security issues. Current versions are 9.x and above. Fix: Upgrade to mocha@^10.x or the latest stable version - Medium · Outdated Sinon Test Library —
package.json - sinon. sinon@^4.4.2 is outdated (released in 2018). While less critical than core dependencies, it may have unpatched vulnerabilities. Fix: Upgrade to sinon@^15.x or the latest stable version compatible with your test suite - Low · Missing Dependency Lock File Information —
Repository root. No package-lock.json or yarn.lock file is visible in the provided file structure. This means reproducible builds cannot be guaranteed, increasing the risk of supply chain attacks through transitive dependency updates. Fix: Ensure package-lock.json (npm) or yarn.lock (yarn) is committed and version controlled. Runnpm installoryarn installto generate and commit lock files. - Low · Codecov Token Exposed in README —
README.md - codecov badge URL. The README contains a codecov.io badge with a token parameter: ?token=wHSAz7N8WA. While codecov tokens are meant to be public for badge functionality, this should be verified as intentional. Fix: Verify this is an intentional public token. Consider using codecov without explicit token parameters if possible, or ensure the token has minimal scope.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.