{"id":1110,"url":"https://github.com/khanlou/Promise","last_synced_at":"2025-07-30T20:31:23.436Z","repository":{"id":10158350,"uuid":"64682247","full_name":"khanlou/Promise","owner":"khanlou","description":"A Promise library for Swift, based partially on Javascript's A+ spec","archived":false,"fork":false,"pushed_at":"2022-07-22T00:57:03.000Z","size":206,"stargazers_count":622,"open_issues_count":4,"forks_count":59,"subscribers_count":17,"default_branch":"master","last_synced_at":"2024-07-18T19:11:41.429Z","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/khanlou.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-08-01T16:13:06.000Z","updated_at":"2024-06-23T15:04:17.000Z","dependencies_parsed_at":"2022-08-07T05:15:31.885Z","dependency_job_id":null,"html_url":"https://github.com/khanlou/Promise","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/khanlou%2FPromise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/khanlou%2FPromise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/khanlou%2FPromise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/khanlou%2FPromise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/khanlou","download_url":"https://codeload.github.com/khanlou/Promise/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":215182686,"owners_count":15840918,"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":"2024-01-05T20:15:39.127Z","updated_at":"2024-08-13T13:30:36.951Z","avatar_url":"https://github.com/khanlou.png","language":"Swift","funding_links":[],"categories":["EventBus","etc"],"sub_categories":["Getting Started","Other free courses","Linter"],"readme":"# Promise\n\nA Promise library for Swift, based partially on [Javascript's A+ spec](https://promisesaplus.com/).\n\n## What is a Promise?\n\nA Promise is a way to represent a value that will exist (or will fail with an error) at some point in the future. This is similar to how an `Optional` represents a value that may or may not be there.\n\nUsing a special type to represent values that will exist in the future means that those values can be combined, transformed, and built in systematic ways. If the system knows what success and what failure look like, composing those asynchronous operations becomes much easier. For example, it becomes trivial to write reusable code that can:\n\n* perform a chain of dependent asynchronous operations with one completion block at the end\n* perform many independent asynchronous operations simultaneously with one completion block\n* race many asynchronous operations and return the value of the first to complete\n* retry asynchronous operations\n* add a timeout to asynchronous operations\n\nPromises are suited for any asynchronous action that can succeed or fail exactly once, such as HTTP requests. If there is an asynchronous action that can \"succeed\" more than once, or delivers a series of values over time instead of just one, take a look at [Signals](https://github.com/JensRavens/Interstellar/) or [Observables](https://github.com/ReactiveX/RxSwift).\n\n## Basic Usage\n\nTo access the value once it arrives, you call the `then` method with a block.\n\n```swift\nlet usersPromise = fetchUsers() // Promise\u003c[User]\u003e\nusersPromise.then({ users in\n    self.users = users\n})\n```\n\nAll usage of the data in the `users` Promise is gated through the `then` method.\n\nIn addition to performing side effects (like setting the `users` instance variable on `self`), `then` enables you do two other things. First, you can transform the contents of the Promise, and second, you can kick off another Promise, to do more asynchronous work. To do either of these things, return something from the block you pass to `then`. Each time you call `then`, the existing Promise will return a new Promise.\n\n```swift\nlet usersPromise = fetchUsers() // Promise\u003c[User]\u003e\nlet firstUserPromise = usersPromise.then({ users in // Promise\u003cUser\u003e\n    return users[0]\n})\nlet followersPromise = firstUserPromise.then({ firstUser in //Promise\u003c[Follower]\u003e\n    return fetchFollowers(of: firstUser)\n})\nfollowersPromise.then({ followers in\n    self.followers = followers\n})\n```\n\nBased on whether you return a regular value or a promise, the `Promise` will determine whether it should transform the internal contents, or fire off the next promise and await its results.\n\nAs long as the block you pass to `then` is one line long, its type signature will be inferred, which will make Promises much easier to read and work with.\n\nSince each call to `then` returns a new `Promise`, you can write them in a big chain. The code above, as a chain, would be written:\n\n```swift\nfetchUsers()\n    .then({ users in\n        return users[0]\n    })\n    .then({ firstUser in\n        return fetchFollowers(of: firstUser)\n    })\n    .then({ followers in\n        self.followers = followers\n    })\n```\n\nTo catch any errors that are created along the way, you can add a `catch` block as well:\n\n```swift\nfetchUsers()\n    .then({ users in\n        return users[0]\n    })\n    .then({ firstUser in\n        return fetchFollowers(of: firstUser)\n    })\n    .then({ followers in\n        self.followers = followers\n    })\n    .catch({ error in\n        displayError(error)\n    })\n```\n\nIf any step in the chain fails, no more `then` blocks will be executed. Only failure blocks are executed. This is enforced in the type system as well. If the `fetchUsers()` promise fails (for example, because of a lack of internet), there's no way for the promise to construct a valid value for the `users` variable, and there's no way that block could be called.\n\n## Creating Promises\n\nTo create a promise, there is a convenience initializer that takes a block and provides functions to `fulfill` or `reject` the promise:\n\n```swift\nlet promise = Promise\u003cData\u003e(work: { fulfill, reject in\n    try fulfill(Data(contentsOf: someURL)\n})\n```\n\nIt will automatically run on a global background thread. \n\nYou can use this initializer to wrap a completion block-based API, like `URLSession`.\n\n```swift\nlet promise = Promise\u003c(Data, HTTPURLResponse)\u003e(work: { fulfill, reject in\n    self.dataTask(with: request, completionHandler: { data, response, error in\n        if let error = error {\n            reject(error)\n        } else if let data = data, let response = response as? HTTPURLResponse {\n            fulfill((data, response))\n        } else {\n            fatalError(\"Something has gone horribly wrong.\")\n        }\n    }).resume()\n})\n```\n\nIf the API that you're wrapping is sensitive to which thread it's being run on, like any UIKit code, be sure to pass add a `queue: .main` parameter to the `work:` initializer, and it will be executed on the main queue.\n\nFor delegate-based APIs, you can can create a promise in the `.pending` state with the default initializer.\n\n```swift\nlet promise = Promise()\n```\n\nand use the `fulfill` and `reject` instance methods to change its state. \n\n## Advanced Usage\n\nBecause promises formalize how success and failure blocks look, it's possible to build behaviors on top of them. \n\n### `always`\n\nFor example, if you want to execute code when the promise fulfills — regardless of whether it succeeds or fails – you can use `always`.\n\n```swift\nactivityIndicator.startAnimating()\nfetchUsers()\n    .then({ users in\n        self.users = users\n    })\n    .always({\n        self.activityIndicator.stopAnimating()\n    })\n```\n\nEven if the network request fails, the activity indicator will stop. Note that the block that you pass to `always` has no parameters. Because the `Promise` doesn't know if it will succeed or fail, it will give you neither a value nor an error.\n\n### `ensure`\n\n`ensure` is a method that takes a predicate, and rejects the promise chain if that predicate fails.\n\n```swift\nURLSession.shared.dataTask(with: request)\n    .ensure({ data, httpResponse in\n        return httpResponse.statusCode == 200\n    })\n    .then({ data, httpResponse in\n        // the statusCode is valid\n    })\n    .catch({ error in \n        // the network request failed, or the status code was invalid\n    })\n```\n\n## Static methods\n\nStatic methods, like `zip`, `race`, `retry`, `all`, `kickoff` live in a namespace called `Promises`. Previously, they were static functions in the `Promise` class, but this meant that you had to specialize them with a generic before you could use them, like `Promise\u003c()\u003e.all`. This is ugly and hard to type, so as of v2.0, you now write `Promises.all`.\n\n### `all`\n\n`Promises.all` is a static method that waits for all the promises you give it to fulfill, and once they have, it fulfills itself with the array of all fulfilled values. For example, you might want to write code to hit an API endpoint once for each item in an array. `map` and `Promises.all` make that super easy:\n\n```swift\nlet userPromises = users.map({ user in\n    APIClient.followUser(user)\n})\nPromises.all(userPromises)\n    .then({\n        //all the users are now followed!\n    })\n    .catch  ({ error in\n        //one of the API requests failed\n    })\n```\n\n### `kickoff`\n\nBecause the `then` blocks of promises are a safe space for functions that `throw`, its sometimes useful to enter those safe spaces even if you don't have asynchronous work to do. `Promises.kickoff` is designed for that.\n\n```swift\nPromises\n\t.kickoff({\n\t\treturn try initialValueFromThrowingFunction()\n\t})\n\t.then({ value in\n\t\t//use the value from the throwing function\n\t})\n\t.catch({ error in\n\t\t//if there was an error, you can handle it here.\n\t})\n```\n\nThis (coupled with `Optional.unwrap()`) is particularly useful when you want to start a promise chain from an optional value. \n\n## Others behaviors\n\nThese are some of the most useful behaviors, but there are others as well, like `race` (which races multiple promises), `retry` (which lets you retry a single promise multiple times), and `recover` (which lets you return a new `Promise` given an error, allowing you to recover from failure), and others.\n\nYou can find these behaviors in the [Promises+Extras.swift](https://github.com/khanlou/Promise/blob/master/Promise/Promise%2BExtras.swift) file.\n\n### Invalidatable Queues\n\nEach method on the `Promise` that accepts a block accepts an execution context with the parameter name `on:`. Usually, this execution context is a queue.\n\n```swift\nPromise\u003cVoid\u003e(queue: .main, work: { fulfill, reject in\n    viewController.present(viewControllerToPresent, animated: flag, completion: {\n        fulfill()\n    })\n}).then(on: DispatchQueue.global(), {\n\treturn try Data(contentsOf: someURL)\n})\n```\n\nBecause `ExecutionContext` is a protocol, other things can be passed here. One particularly useful one is `InvalidatableQueue`. When working with table cells, often the result of a promise needs to be ignored. To do this, each cell can hold on to an `InvalidatableQueue`. An `InvalidatableQueue` is an execution context that can be invalidated. If the context is invalidated, then the block that is passed to it will be discarded and not executed.\n\nTo use this with table cells, the queue should be invalidated and reset on `prepareForReuse()`.\n\n```swift\nclass SomeTableViewCell: UITableViewCell {\n    var invalidatableQueue = InvalidatableQueue()\n        \n    func showImage(at url: URL) {\n        ImageFetcher(url)\n            .fetch()\n            .then(on: invalidatableQueue, { image in\n                self.imageView.image = image\n            })\n    }\n    \n    override func prepareForReuse() {\n        super.prepareForReuse()\n        invalidatableQueue.invalidate()\n        invalidatableQueue = InvalidatableQueue()\n    }\n\n}\n```\n\nWarning: don't chain blocks off anything that is executing on an invalidatable queue. `then` blocks that return `Void` won't stop the chain, but `then` blocks that return values or promises _will_ stop the chain. Because the block can't be executed, the result of the next value in the chain won't be calculable, and the next promise will remain in the `pending` state forever, preventing resources from being released.\n\n## Ease of Use\n\nI made several design decisions when writing this `Promise` library, erring towards making the library as easy to use as possible.\n\n### Simplified Naming\n\nOther promise libraries use the functional names for `then`, such as `map` and `flatMap`. The benefit to using these monadic functional terms is minor, but cost, in terms of understanding, is high. In this library, you call `then`, and return anything you need to, and the library figures out how to handle it.\n\n### Error Parameterization\n\nOther promise libraries allow you to define what type the error each promise will return. In theory, this a useful feature, allowing you to know what type the error will be in `catch` blocks.\n\nIn practice, this is stifling. In practice, if you're using errors from two different domains, you have to either a) use a lowest common denominator error, like `NSError`, or b) call a method like `mapError` to convert the error from one domain to another.\n\nAlso note that Swift's built-in error handling system doesn't have typed errors, opting for pattern matching instead.\n\n### Throwing\n\nLastly, you can use `try` and `throw` from within all the blocks, and the library will automatically translate it to a promise rejection. This makes working with APIs that throw much more easily. To extend our `URLSession` example, we can use the throwing JSONSerialization API easily.\n\n```swift\nURLSession.shared.dataTask(with: request)\n    .ensure({ data, httpResponse in httpResponse.statusCode == 200 })\n    .then({ data, httpResponse in\n        return try JSONSerialization.jsonObject(with: data)\n    })\n    .then({ json in\n        // use the json\n    })\n```\n\nWorking with optionals can be made simpler with a little extension.\n\n```swift\nstruct NilError: Error { }\n\nextension Optional {\n    func unwrap() throws -\u003e Wrapped {\n        guard let result = self else {\n            throw NilError()\n        }\n        return result\n    }\n}\n```\n\nBecause you're in an environment where you can freely throw and it will be handled for you (in the form of a rejected Promise), you can now easily unwrap optionals. For example, if you need a specific key out of a json dictionary:\n\n```swift\n.then({ json in\n    return try (json[\"user\"] as? [String: Any]).unwrap()\n})\n```\n\nAnd you will transform your optional into a non-optional.\n\n### Threading Model\n\nThe threading model for this library is dead simple. `init(work:)` happens on a background queue by default, and every other block-based method (`then`, `catch`, `always`, etc) executes on the main thread. These can be overridden by passing in a `DispatchQueue` object for the first parameter.\n\n```swift\nPromise\u003cVoid\u003e(work: { fulfill, reject in\n    viewController.present(viewControllerToPresent, animated: flag, completion: {\n        fulfill()\n    })\n}).then(on: DispatchQueue.global(), {\n\treturn try Data(contentsOf: someURL)\n}).then(on: DispatchQueue.main, {\n\tself.data = data\n})\n```\n\n## Installation\n\n### [CocoaPods](http://cocoapods.org/)\n\n1. Add the following to your [Podfile](http://guides.cocoapods.org/using/the-podfile.html):\n\n    ```rb\n    pod 'Promises'\n    ```\n\n2. Integrate your dependencies using frameworks: add `use_frameworks!` to your Podfile. \n3. Run `pod install`.\n\n## Playing Around\n\nTo get started playing with this library, you can use the included `Promise.playground`.  Simply open the `.xcodeproj`, build the scheme, and then open the playground (from within the project) and start playing.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkhanlou%2FPromise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkhanlou%2FPromise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkhanlou%2FPromise/lists"}