Implementing Promises in Swift
I recently have been looking for some resources on how to implement a promise in Swift, and because I did not find any good articles about it, I thought I could write one instead. In this article we’ll implement our own
Promise type in order to understand the logic behind it.
Note that the implementation is far from production ready and should not be used as is. For instance, our promise will not provide any error mechanism and threading will not be covered. I’ll link useful resources and complete implementations at the end of this article for those who want to dig deeper.
Note: To make this tutorial a little more interesting, I chose to do it in TDD. We will write tests first and make them pass one by one.
Our first test
Let’s write our first test.
In this test we want to implement that the function passed to the initializer of the promise is called immediately.
Note: For those who may wonder, we do not use any test framework here, but a custom method
test that simulates assertions in the playground (gist).
When we run the playground, the compiler raises a first error:
Fair enough, we need to define the
The error now becomes:
We have to define an initiliazer that takes a closure as an argument. And this closure should be called immediately.
This makes our first test pass! We have almost nothing for now, but be patient, our implementation will grow in the next section!
We can comment this test out as the implementation of
Promise will change a bit in the future.
The bare minimum
Our second test is the following:
The test is quite simple, but we add some content to the
Promise class. We create a promise with a resolution handler (the
resolve parameter of the closure) and call it right away with a value.
In a second time, we use the
then method on the promise to access the value and make an assertion about it.
Before diving into the implementation, we have to introduce a slightly different test at the same time.
Contrary to the test
resolve method is called after a delay. That means the value will not be available in the
then right away (because the 0.1 seconds are not passed yet when we call
We can start understanding the “problem” here. We have to deal with asynchronicity.
Our promise is a state machine. When it is first created, the promise is in a
pending state. Once the
resolve method is called with a value, our promise pass in a
resolved state and store this value.
then can be called anytime, whatever the internal state of the promise (meaning before or after the promise has a value). If the promise is in
pending state and we call
then on it, the value is not available, so we have to store the callback parameter. And once the promise becomes
resolved, we can trigger this same callback with the resolved value.
Now that we understand a little better what we have to implement, let’s start by fixing the compiler issues.
We have to make our
Promise type generic. Indeed, a promise is associated with a predefined type and will hold a value of this type once resolved.
Now the error becomes:
We have to provide a
resolve function to the closure passed in the initializer (the executor).
Note here that the resolve parameter is a function that consume a value:
(Value) -> Void. This function will be called by the outside world once the value is determined.
The compiler is still not happy because we need to provide a
resolve function to the
executor. Let’s create one that will be
We will implement
resolve in a moment, when all the errors will be taken care of.
The next one is simple, the method
then is not defined yet.
Let’s fix that.
Now that the compiler is happy, let’s go back where we were before.
We previously said that a
Promise is a state machine with a
pending and a
resolved state. We can define these states with an enum:
The beauty of Swift makes it possible to store the value of the promise directly in the enum.
Now we have to define a default state of
.pending in our
Promise implementation. And we also need a private function that can update the state in case the promise is still in a
Note that the
updateState(to:) function first checks if the promise is in the
.pending state. If the promise is already in the
.resolved state, it can’t move to another state and will stay
Now it’s time to update the state of the promise when needed, meaning when the
resolve function is called from the outside world with a value.
We are almost done, but there is still the
then method to implement. We said we had to store the callback parameter and call this callback when the promise resolves. Let’s implement that.
We define an instance variable
callback that holds the callback while the promise is
.pending. We also create a method
triggerCallbackIfResolved that first checks if the state is
.resolved, unwraps the associated value, and pass it to the callback. This method is called at two locations. In the
then method if the promise is already resolved at the time we call
then. And in the
updateState method, because that’s where the promise updates its state from
With these modifications, our tests pass successfully.
We are on the right path, but there is still a slight change we have to make to get a first real
Promise implementation. Let’s look at the tests first.
This time we call
then twice on the promise.
Let’s look at the tests outputs.
The tests pass, but you may have spotted the issue. The test
2.2 has only one assertion, but should have two.
If we think about it, it’s logical. Indeed, in the async version (
2.2), when the first
then is called the promise is still
.pending. As we have seen earlier, we store the callback of the first
then. But when we call the
then for the second time, the promise hasn’t resolved yet and is still
.pending, so we erase the callback with the new one. Only the second callback will be executed, the first one being forgotten. That makes the test pass, but with only one assertion instead of two.
The solution here is to store an array of callbacks and to trigger all the callbacks when the promise resolves.
Let’s update our solution with this small update.
The tests now pass with both two assertions.
Congratulations! We have created the base of our
Promise class. You can already use it to abstract asynchronicity but it’s still limited.
Note: if we take a look at the global picture here, we can see that the
then we have defined could be renamed
observe. Its purpose is to consume the value of the promise once resolved, but it does not return anything. Meaning we can’t chain promises for now.
In the next sections we will create overloads of
then in order to return new promises or new values along the way.
Promise implementation would not be complete if we can’t chain multiple promises.
Let’s look at the test that will help us implement this feature.
We can see here that the first
then creates a new
Promise with a whole new value and returns it. The second
then(the one we defined in the previous section, that we called
observe) is chained to access the new value (that will hold
This immediately raises an error in the console.
We have to create an overload of
then that takes a function that returns a promise. And in order to chain other calls of
then, the method has to return a promise too. The prototype of this new method
then is the following.
Note: Attentive readers may have spotted that we are implementing
flatMap on the
Promise type here. In the same way
flatMap is defined for
Array, we can define it for the
The “difficulty” starts here. Let’s walk through the implementation of this “flatMap”
then step by step.
- We have to return a
- What gives us such a promise? The
onResolvedtakes a value of type
Valuein parameter. How can we get this value? We can use the previously defined
observe”) to access it when available.
If we write this down, here what we get for now:
We are almost there. There is still a small problem to fix: the
promise variable is captured in the closure passed to
then. We can’t use it as a return value for the function.
The trick here is to create a wrapping
Promise<NewValue> that will execute what we wrote so far, and that will resolve at the same time the
promise variable is resolved. In other words, when the promise provided by the
onResolved method will resolve and get a value from the outside, the wrapping promise will resolve too and get the same value.
That may be a little abstract, but if we write it, we will see it better:
If we clean the code a bit we now have these two methods :
And finally, the test pass.
If you can implement
flatMap on a type, you can implement
map on this same type using
flatMap. What does
map looks like for our
The test we will use here is the following:
Note here that the first
then that is used does not return a
Promise anymore, but transforms the value it receives. This is a new
then and corresponds to the
map version we want to add.
The compiler emits an error saying we have to implement this method.
The prototype is very close to the
flatMap version, the only difference is that we return a
NewValue instead of a
Promise<NewValue> in the
We said earlier that we can use
flatMap to implement
map. In our case we see we need to return a
Promise<NewValue>. If we use the “flatMap”
then of the previous section and create a promise that directly resolved with the mapped value, we have finished. Let’s prove it.
Once again the test pass.
If we remove the comments and look at what we achieved, we have three
then methods implemented that can be used and chained.
Example of use
We will stop here for the implementation. Our
Promise class is complete enough to demonstrate what we can do with it.
Let’s imagine we have some users in our app, that we store in the following struct:
Let’s also say we have two methods at our disposable, one that fetch a list of user ids, and one that fetch a user from its id. And let’s say we want to display the name of the first user.
Here is how we can do it very simply with our implementation, using the three
then we previously defined.
The code becomes highly readable, flat and concise!
That’s the end of this article, I hope you liked it.
You can find the whole code in this gist. And if you want to dig deeper, here are the sources I used.