{"id":27938536,"url":"https://github.com/willowtreeapps/pinkypromise","last_synced_at":"2025-06-23T09:07:44.599Z","repository":{"id":6211110,"uuid":"54035197","full_name":"willowtreeapps/PinkyPromise","owner":"willowtreeapps","description":"A tiny Promises library.","archived":false,"fork":false,"pushed_at":"2023-04-12T06:13:27.000Z","size":391,"stargazers_count":33,"open_issues_count":4,"forks_count":4,"subscribers_count":137,"default_branch":"develop","last_synced_at":"2025-06-09T16:15:22.491Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/willowtreeapps.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-03-16T13:43:21.000Z","updated_at":"2023-05-02T15:39:52.000Z","dependencies_parsed_at":"2025-05-07T08:59:20.718Z","dependency_job_id":null,"html_url":"https://github.com/willowtreeapps/PinkyPromise","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/willowtreeapps/PinkyPromise","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willowtreeapps%2FPinkyPromise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willowtreeapps%2FPinkyPromise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willowtreeapps%2FPinkyPromise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willowtreeapps%2FPinkyPromise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/willowtreeapps","download_url":"https://codeload.github.com/willowtreeapps/PinkyPromise/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willowtreeapps%2FPinkyPromise/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261449798,"owners_count":23159806,"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","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":[],"created_at":"2025-05-07T08:49:06.783Z","updated_at":"2025-06-23T09:07:39.586Z","avatar_url":"https://github.com/willowtreeapps.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Legacy note\n\nPinkyPromise has retired in success. Its features have been implemented by the Swift language and first party libraries—the best case scenario for long term support.\n\nIf you've been a PinkyPromise user, please look into your migration path:\n\n- Results: Use the Swift standard library `Result` type.\n  - But you can often do without `Result`. An `async` function can return or throw, so there is now a way to deliver success and failure results with plain syntax and no wrapping type.\n- Promises: Use Swift Concurrency.\n  - `async` functions provide regular Promise functionality with greater safety.\n  - Where you would have adapted a completion block function by wrapping it in a Promise, first look to see if there is now an `async` version of the function. If not, use `withCheckedContinuation` or `withCheckedThrowingContinuation`. They require the same complete-once contract but offer runtime checks on that condition.\n  - Structured concurrency can run many async functions concurrently, like `zip` and `zipArray` would do with Promises. Cancellation is available too.\n  - Actor isolation can, among other things, require work to happen on the main thread. If you had to pair PinkyPromise with Dispatch, you can migrate away from Dispatch too and get greater safety against data races.\n  - The `AsyncSequence` type lifts `for` loops into the async world to consume multiple-result tasks, while this library only offered single-result tasks. The Combine framework's `AsyncPublisher` type bridges from RxSwift-like observable streams to `AsyncSequence`.\n  - [Swift Async Algorithms](https://github.com/apple/swift-async-algorithms) has ways to build on async work tasks in useful ways, like debouncing a sequence.\n\nThanks for using PinkyPromise!\n\n# PinkyPromise\nA tiny Promises library.\n\n## Summary\n\nPinkyPromise is an implementation of [Promises](https://en.wikipedia.org/wiki/Futures_and_promises) for Swift. It consists of two types:\n\n- `Result` - A value or error. We use Swift's built in [Result Type](https://developer.apple.com/documentation/swift/result)\n- `Promise` - An operation that produces a Result sometime after it is called. Promises can be composed and sequenced.\n\nWith PinkyPromise, you can run complex combinations of asynchronous operations with safe, clean, Swifty code.\n\n## Installation\n\n- With Carthage: `github \"willowtreeapps/PinkyPromise\"`\n- With Cocoapods: `pod 'PinkyPromise'`\n- Manually: Copy the files in the `Sources` folder into your project.\n\n## Should I use this?\n\nPinkyPromise:\n\n- Is lightweight\n- Is tested\n- Embraces the Swift language with airtight type system contracts and `throw` / `catch`\n- Embraces functional style with immutable values and value transformations\n- Is a great way for Objective-C programmers to learn functional style in Swift\n- Can be extended with your own Promise transformations\n\nPinkyPromise is meant to be a lightweight tool that does a lot of heavy lifting. More elaborate implementations include [Result](https://github.com/antitypical/Result) and [PromiseKit](http://promisekit.org).\n\n## Learning\n\nStart with the [Examples](#examples) section below.\n\nWe've also written a playground to demonstrate the benefits and usage of PinkyPromise. Please clone the repository and open `PinkyPromise.playground` in Xcode.\n\nA natural next step beyond Results and Promises is the [Observable](https://www.youtube.com/watch?v=looJcaeboBY) type. You might use PinkyPromise as a first step toward learning [RxSwift](https://github.com/ReactiveX/RxSwift), which we recommend.\n\n## Examples\n\nA Promise is best at running an asynchronous operation that can succeed or fail.\n\nA Result is best at representing a success or failure from such an operation.\n\n### Why Result?\n\nThe usual asynchronous operation pattern on iOS is a function that takes arguments and a completion block, then begins the work. The completion block will receive an optional value and an optional error when the work completes:\n\n````swift\nfunc getString(withArgument argument: String, completion: ((String?, ErrorType?) -\u003e Void)?) {\n    …\n    if successful {\n        completion?(value, nil)\n    } else {\n        completion?(nil, error)\n    }\n}\n\ngetString(withArgument: \"foo\") { value, error in\n    if let value = value {\n        print(value)\n    } else {\n        print(error)\n    }\n}\n````\n\nThis is a loose contract not guaranteed by the compiler. We have only assumed that `error` is not nil when `value` is nil.\n\nCompare with the standard Swift pattern for failable synchronous methods: A function like `Data(contentsOf:options:)` will either return a value or throw an error, not both, and not neither, and not optionally. This is an airtight contract. But you can't use that pattern in asynchronous calls, because you can only throw backward out of the function you're in, not forward into a completion block.\n\nHere's how you'd write that asynchronous operation with a tighter contract, using Result. The Result is a success or failure. It can be created with `return` or `throw`, and inspected with `value`, which will either return or throw.\n\n````swift\nfunc getStringResult(withArgument argument: String, completion: ((Result\u003cString, Error\u003e) -\u003e Void)?) {\n    …\n    completion?(Result {\n        if successful {\n            return value\n        } else {\n            throw error\n        }\n    })\n}\n \ngetStringResult(withArgument: \"foo\") { result in\n    do {\n        print(try result.get())\n    } catch {\n        print(error)\n    }\n}\n````\n\nUnder the hood, `Result\u003cT, Error\u003e` is an `enum` with two cases: `.Success(T)` and `.Failure(Error)`. It's possible to create a Result using an enum case and inspect it using `switch`. But since Result represents a returned value or a thrown error, we prefer to use it in the style shown above.\n\n### Why Promise?\n\nPromises are useful for combining many asynchronous operations into one. To do that, we need to be able to create an asynchronous operation without starting it right away.\n\nTo make a new Promise, you create it with a task. A task is a block that itself takes a completion block, usually called `fulfill`. The Promise runs the task to do its work, and when it's done, the task passes a `Result` to `fulfill`. (Hint: The task used to create this Promise is the same as the body of `getStringResult(withArgument:)`.)\n\n````swift\nfunc getStringPromise(withArgument argument: String) -\u003e Promise\u003cString\u003e {\n    return Promise { fulfill in\n        …\n        fulfill(Result {\n            if successful {\n                return value\n            } else {\n                throw error\n            }\n        })\n    }\n}\n\nlet stringPromise = getStringPromise(withArgument: \"bar\")\n````\n\n`stringPromise` has captured its task, and the task has captured the argument. It is an operation waiting to begin. So with Promises you can create operations and then start them later. You can start them more than once, or not at all.\n \nNext, we ask `stringPromise` to run by passing a completion block to the `call` method. `call` runs the task and routes the Result back to the completion block. When the Promise completes, our completion block will receive the Result, and can get the value or error with `try` and `catch`.\n\n````swift\nstringPromise.call { result in\n    do {\n        print(try result.get())\n    } catch {\n        print(error)\n    }\n}\n````\n\nAs we've seen, with Promises, supplying the arguments and supplying the completion block are separate events. The greatest strength of a Promise is that in between those two events, the task-to-be-done exists as an immutable value. And in functional style, immutable values can be transformed and combined.\n\nHere is an example of a complex Promise made of several Promises:\n\n````swift\nlet getFirstThreeChildrenOfObjectWithIDPromise =\n    getStringPromise(withArgument: \"baz\") // Promise\u003cString\u003e\n    .flatMap { objectID in\n        // String -\u003e Promise\u003cModelObject\u003e\n        Queries.getObjectPromise(withID: objectID)\n    }\n    .map { object in\n        // ModelObject -\u003e [String]\n        let childObjectIDs = object.childObjectIDs\n        let count = max(3, childObjectIDs.count)\n        return childObjectIDs[0..\u003ccount]\n    }\n    .flatMap { childObjectIDs in\n        // [String] -\u003e Promise\u003c[ModelObject]\u003e\n        zipArray(childObjectIDs.map { childObjectID\n            // String -\u003e Promise\u003cModelObject\u003e\n            Queries.getObjectPromise(withID: childObjectID)\n        })\n    }\n````\n\n`getFirstThreeChildrenOfObjectWithIDPromise` is a single asynchronous operation that consists of many small operations. It:\n\n1. Tries to get a String for an object ID.\n2. If successful, runs an API request for the object with that ID.\n3. If successful, collects up to three child IDs from the object.\n4. If successful, runs simultaneous requests for each child object, producing an array.\n5. Produces either a list of up to three child objects, or an error from any step of the process.\n\nEven though this operation has many steps that depend on prior operations' success, we don't have to coordinate them by writing multiple completion blocks. Instead, we just handle the final result, using the tight contract afforded by Result:\n\n````swift\ngetFirstThreeChildrenOfObjectWithIDPromise.call { [weak self] result in\n    do {\n        self?.updateViews(withObjects: try result.get())\n    } catch {\n        self?.showError(error)\n    }\n}\n````\n\n## Tests\n\nWe intend to keep PinkyPromise fully unit tested.\n\nYou can run tests in Xcode, or use `bundle exec fastlane run_tests` with [Fastlane](https://fastlane.tools).\n\nWe run continuous integration on [CircleCI](https://circleci.com/gh/willowtreeapps/PinkyPromise).\n\n## Roadmap\n\n- More Promise transformations?\n\n## Contributing to PinkyPromise\n\nContributions are welcome. Please see the [Contributing guidelines](CONTRIBUTING.md).\n\nPinkyPromise has adopted a [code of conduct](CODE_OF_CONDUCT.md) defined by the [Contributor Covenant](http://contributor-covenant.org), the same used by the [Swift language](https://swift.org) and countless other open source software teams.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillowtreeapps%2Fpinkypromise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwillowtreeapps%2Fpinkypromise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillowtreeapps%2Fpinkypromise/lists"}