# Promise: a JavaScript-style promise library for Swift

> Promise is a Swift library modelled partly on the JavaScript Promises A+ specification, giving Swift a then, catch, always and ensure chain over values that arrive later. The type system is the interesting part, because a failed chain cannot produce a value.

**khanlou/Promise** — A Promise library for Swift, based partially on Javascript's A+ spec

- Repository: https://github.com/khanlou/Promise
- Stars: 615 · Forks: 57
- Language: Swift
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/khanlou-promise

## What a promise buys over a completion block

The README starts from the definition, and the definition is the selling point. A promise is a way to represent a value that will exist, or will fail with an error, at some point in the future, and the comparison the author draws is with Optional, which represents a value that may or may not be there. The point of using a special type is that those future values can be combined, transformed and built in systematic ways once the system knows what success and what failure look like. The list of what becomes easy follows directly, and it is a useful checklist for deciding whether this library is relevant to you. Reusable code can perform a chain of dependent asynchronous operations with one completion block at the end, or perform many independent asynchronous operations simultaneously with one completion block, or race many operations and return the value of the first to complete, and it can retry operations and add a timeout to them. The scope condition is stated just as clearly. Promises suit any asynchronous action that can succeed or fail exactly once, and the README gives HTTP requests as the example. If the action can succeed more than once, or delivers a series of values over time rather than a single one, the README sends you to signals or observables instead.

## then does three jobs: side effect, transform, and continuation

The access point is then, and the README's explanation of what a then block can return is what makes the library composable. You call then with a block to get at the value once it arrives, and the block can do three things. It can perform a side effect, such as assigning to an instance variable. It can transform the contents of the promise. And it can kick off another promise to do more asynchronous work. The return value decides which: based on whether you return a regular value or a promise, the promise determines whether it should transform the internal contents or fire off the next promise and await its results. Each call to then returns a new promise, so the calls chain. The README shows the same logic twice, first as a sequence of separately bound promises, each assigned to a named variable, and then as a single chain.

```swift
fetchUsers()
    .then({ users in
        return users[0]
    })
    .then({ firstUser in
        return fetchFollowers(of: firstUser)
    })
    .then({ followers in
        self.followers = followers
    })
```

One ergonomic detail is worth knowing before you write any of it. As long as the block you pass to then is one line long, its type signature will be inferred, which the README says makes promises much easier to read and work with. So the style the library encourages is short blocks, and a long block costs you type inference.

## The type system is where the error handling actually happens

This is the part of the library that goes beyond a JavaScript port, and the README is specific about it. To catch errors created along the way you add a catch block, and if any step in the chain fails, no more then blocks will be executed, only failure blocks are executed. The enforcement happens in the type system as well, and the example explains why with a concrete case. If the fetchUsers promise fails, for example because of a lack of internet, there is no way for the promise to construct a valid value for the users variable, and therefore no way that the block could be called. The consequence for a Swift developer is the point of the library. A failing chain is not a runtime surprise, it is a state the compiler can see, and the block that expects a value simply never runs. The difference from a completion handler is that a completion handler receives an optional value and an error together, so every call site has to check, whereas here the failure branch is a different type of continuation. The trade is verbosity in a different place. Where a JavaScript promise is one type with a runtime check, this one is a type that carries the failure, so the code that has to handle the error looks structurally different from the code that does not.

## Creating promises: the work initializer, the thread, and the pending state

The creation API has two forms and the difference between them is about who owns the work. The convenience initializer takes a block and hands you functions to fulfill or reject the promise, and it will automatically run on a global background thread. The README says you can use it to wrap a completion block based API such as URLSession, and gives the shape of that wrapper with a data task and a completion handler that either rejects with the error, fulfills with the data and the response, or traps. There is a thread caveat worth internalising, because it is the kind of thing that only shows up as a crash. If the API you are wrapping is sensitive to which thread it runs on, such as any UIKit code, you pass a queue parameter set to .main to the work initializer and it will be executed on the main queue. The second form is for delegate based APIs, where you create a promise in the pending state with the default initializer and then use the fulfill and reject instance methods to change its state later.

```swift
let promise = Promise()
```

The pending state is what makes the library usable with callback and delegate patterns that do not fit the block shape, and it is the piece that a purely functional port would not have.

## always, ensure, and why the static methods moved to a namespace

Two extension points build behaviours on top of the promise core, and both address a specific need. The always method runs its block when the promise fulfils regardless of whether it succeeds or fails, which is the shape of a spinner that has to stop either way. The README notes that the block passed to always has no parameters, because the promise does not know whether it will succeed or fail and so gives neither a value nor an error.

```swift
activityIndicator.startAnimating()
fetchUsers()
    .then({ users in
        self.users = users
    })
    .always({
        self.activityIndicator.stopAnimating()
    })
```

The ensure method takes a predicate and rejects the promise chain if that predicate fails, which the README demonstrates with a status code check, so an HTTP response that is not a success becomes a rejection in the chain and lands in the catch block. The third piece is an API change worth knowing about. Static methods such as zip, race, retry, all and kickoff used to live in the Promise class, which meant you had to specialise them with a generic before you could use them, something like Promise<()>.all, which the README calls ugly and hard to type. As of version 2.0 you write Promises.all instead, and the static methods live in a namespace called Promises. The change was driven by ergonomics, and it is the kind of break that shows up in a project that upgrades across that version.

## Promises.all and the other combinators, against signals and observables

The combinators are the reason to reach for a promise library rather than writing your own. Promises.all waits for all the promises you give it to fulfil and then fulfils itself with the array of all the fulfilled values, which is the shape of a screen that shows a list once every request has returned. The README's example for it is hitting an API endpoint once for each item. The other members of the namespace cover the remaining patterns from the introduction, with zip for pairing, race for the first to complete, retry for repeating a failed operation, and kickoff, which the README names in the same list. The alternative to all of this is not a different library but a different abstraction, and the README points at it directly. If an asynchronous action can succeed more than once, or delivers a series of values over time instead of one, that is not a promise, and the README points to Signals and to Observables, with RxSwift named as the observable option. The difference in approach is exact. A promise models a single outcome, so the only question its combinators answer is which of several one-shot results wins or whether all of them have arrived. A signal or an observable models a stream, so it can express repeated values, cancellation and subscription, and the failure case becomes one more event rather than a terminal state. Picking between them is not a style choice, it is a question about whether the operation completes once or continues.

## SwiftPM, CocoaPods, and a release line that stopped in 2018

The packaging is ordinary and covers the two ways Swift projects are distributed. The repository has a Package.swift file for Swift Package Manager, an Xcode project for the development workflow, a playground for trying the library out, a test target, and a podspec, so CocoaPods is supported as well. The Gemfile and Gemfile.lock alongside them are the usual Ruby tooling for scripted release tasks, and there is a LinuxMain.swift file, which was the convention for running tests on Linux before the Swift toolchain handled test discovery itself. The maintenance picture is the part to weigh. The repository is not archived, and the last push was on 2026-06-23, so the code is still being touched. The release tags, though, are old, with 2.0 from 2017-09-30 and 2.0.1 from 2018-02-05. The licence is MIT, so the code can be used, modified and redistributed, including inside a closed source product, with the notice retained. For a small library of this shape, that is not a problem in itself, since the implementation is a few hundred lines of well understood code, but a team that has standardised on the async and await keywords in modern Swift should weigh the language features before adding a dependency whose last tag predates them.

## Conclusion

Adopt Promise if you are writing Swift code that performs one-shot asynchronous work, an HTTP request or a file load, and you want the chaining and error handling to live in the type system rather than in nested completion closures. It does not fit work that produces many values over time, since the README itself points at signals and observables for that, and it does not fit a long lived app that has standardised on the async and await keywords of modern Swift without a decision about which promise model you want. Before you add the dependency, check the release line, because the newest tag is 2.0.1 from 2018-02-05 while the last push was on 2026-06-23, and read the README section on Promises.all if you plan to fan out requests.

## FAQ

### What is the Promise library for Swift?

It is a Swift library for representing a value that will exist or fail with an error in the future, modelled partly on the JavaScript Promises A+ specification. The README draws the comparison with Optional and argues that knowing what success and failure look like makes asynchronous composition systematic.

### When should I not use a promise in Swift?

The README says promises suit asynchronous actions that can succeed or fail exactly once, such as HTTP requests. If the action can succeed more than once, or delivers a series of values over time, it points to signals or to observables, with RxSwift named for the latter.

### How do I create a Promise in Swift?

Use the convenience initializer that takes a work block with fulfill and reject functions, which runs automatically on a global background thread, and pass a queue parameter set to .main if the wrapped API is sensitive to the thread. For delegate based APIs, create a promise in the pending state with the default initializer and change its state with the fulfill and reject instance methods.

### What do always and ensure do in the Promise library?

always runs its block whether the promise fulfils or fails, and the block takes no parameters because the promise gives neither a value nor an error. ensure takes a predicate and rejects the promise chain if that predicate fails, which the README shows with an HTTP status code check.

### How does Promises.all work and why is it in its own namespace?

Promises.all waits for all the promises you give it to fulfil and then fulfils itself with the array of all the fulfilled values. The static methods zip, race, retry, all and kickoff used to be static functions on the Promise class, which required specialising them with a generic, so as of version 2.0 they live in a namespace called Promises.

## Sources

- [Issues](https://github.com/khanlou/Promise/issues)
- [khanlou/Promise on GitHub](https://github.com/khanlou/Promise)
- [License: MIT](https://github.com/khanlou/Promise/blob/master/LICENSE)
- [README](https://github.com/khanlou/Promise/blob/master/README.md)
- [Releases](https://github.com/khanlou/Promise/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/khanlou-promise
