Promises are one of the best ways to handle asynchronous code. The best example of asynchronous code is API making a call to some endpoint and getting the data.
Now, these calls are the basis of the modern internet. This is how Amazon's website receives all product data from the back end. The data is always stored in a server, so it takes one to two seconds to show the data.
That's a lot of time in programming, and our code doesn't wait before executing further. Once it receives the data, it's displayed.
In this post, we'll learn about PromiseKit, through which we can implement promises in Swift.
We'll create a small app and see an example without promise first. After that, we'll implement promises through PromiseKit.
Additionally, we'll see a complex example of multiple promises. Finally, we're going to write a small test for our app.
What Is a PromiseKit Function?
PromiseKit is an open-source package through which we implement promises in Swift. We can also write asynchronous code without promises, and that's by using callbacks, which we've done in our Alamofire post.
The problem with callbacks is that the code is complex. And if we have to do several asynchronous calls, the code becomes more complex. This is sometimes also referred to as Callback Hell.
In the next section, we're going to look into doing an asynchronous call with callbacks and follow it by doing an asynchronous call with promises.
Project Setup
Let’s first create a new project by opening XCode. After that click on New > Project.
In the next pop-up, click on App and then the Next button.
In the next pop-up, give any product name, which is PromiseKitDemo in our case. The interface should be Storyboard, and we also need to select the checkbox to Include Tests because we’re going to use both Storyboard and tests in the app.
In the next pop-up, click on the Create button after choosing a suitable folder.
Our project will open in XCode and will look like this:
Installing Dependencies
We're going to use both PromiseKit and Alamofire in this project. The question is "How do I use PromiseKit?" and the answer is simple—we can use any external package in Swift by installing it through Podfile or the Swift Package Manager.
In this post, we're going to use the Swift Package Manager to install both.
Now, from the Alamofire GitHub page, get the Git link by clicking on "Code."
Next, go to the root of the project. After that, click on the project and go to the Package Dependencies tab.
Then, click on the + icon.
A pop-up will be opened to add packages. Here, we'll see a lot of Swift packages.
In the search bar give the Git link for Alamofire, which we copied earlier. We'll get the package, and then click on the Add Package button.
A pop-up will appear to confirm we want to add the package. Here, we'll click on the Add Package button.
Next, we'll add PromiseKit to our project. So, go to the PromiseKit GitHub link and get the Git URL by clicking on "Code."
Again, click on the + icon in the Swift Package Manager. Then, in the pop-up, give the PromiseKit Git link. Upon getting the package, click on the Add Package button.
Again, a pop-up will appear to confirm we want to add the package. Here, also click on the Add Package button.
Now, in XCode, we'll see both packages have been installed.
API Call Through Callbacks
Now, we'll first do the API call using the earlier used callback method.
Create a new Swift file in the root folder from XCode.
In the next pop-up, give the new file a name: APIFetchHandler.
Now, in the file APIFetchHandler.swift, first import Alamofire. After that, inside the class APIFetchHandler, we'll create its static instance.
Next, we have the function fetchAPIData(), in which we're using the AF.request() method to do an API call to the posts JSON placeholder endpoint.
In case of the success of the API request, we'll take the data and use JSONDecoder() to convert it into JSON. In the case of failure, we'll just print it.
Also, notice that we're using a model called Codable, which has a structure that needs to be the same as the JSON placeholder endpoint object.
Now, in the ViewController.swift file, call the fetchAPIData() function:
Then, we'll follow the instructions in the section App Layout with Storyboard from the Alamofire post.
Back in the APIFetchHandler.swift file, pass the parameter of a handler. Now, instead of printing jsonData, we're calling it with the function handler. This is the concept of the callback function to use the API data.
Now, in the ViewController.swift file, we've changed the call to fetchAPIData. Here, we're storing the data in the apiResult variable. The data we're getting back here is because of the callback function passed in parameters to the fetchAPIData function in the APIFetchHandler.swift file.
We've also created an extension of ViewController from the Delegate and DataSource. It'll be used to show the data on the simulator.
Now, upon running the app, we'll see the titles from the JSON placeholder API endpoint on display.
API Call Through Promises
Back in the APIFetchHandler.swift file, we'll first import PromiseKit. Then, we'll create a new function called fetchDataWithPromise(), where we're also returning a promise.
Since a promise has three states—pending, fulfill, and reject—we'll first create a resolver and assign it to the pending state.
Next, we'll do the API call with Alamofire, as described earlier. But once we get the response, we have a code for the error. The code will be thrown if we have an error that involves resolver.reject(error).
If the API call is successful and we get the data, we'll send it back with resolver.fulfill(posts).
Now, in ViewController.swift file, we'll call the function fetchDataWithPromise(). Notice that we have the done and catch block. The done block is run if fulfill is executed from the fetchDataWithPromise(). And the catch block is run if reject is executed from the fetchDataWithPromise().
In our case, since we had success in the API call and the fulfill was executed, we'll go to the posts printed on the console.
Now, we'll also check the case in which we get the error. But first, we need to have an enum containing errors in the APIFetchHandler.swift file.
Also, we've updated the error to take from the enum in resolver.reject() in the APIFetchHandler.swift file. The main thing here is that we've made the API URL endpoint wrong.
Now, when we run our app again, an error will be printed. This time, reject is executed from the fetchDataWithPromise(). And the catch statement is run in the ViewController.swift file.
Promises in Parallel
Back in our APIFetchHandler.swift file, we'll add a function called fetchUsersWithPromise(). This is the same as our earlier function of fetchDataWithPromise() but calls the different JSON placeholder endpoint of users.
Note that we've also made a new model called "User" for it.
Now, in the ViewController.swift file, we'll first have two variables: getAllUsers and getAllPosts. They'll have the data from the call to the functions fetchUsersWithPromise() and fetchDataWithPromise(), respectively.
Next, we have a "when" statement, which will take both these variables. It'll execute them in parallel and wait for them to finish.
Inside it, in the done statement, we'll print both of them. We also have a catch statement in case of failure.
Upon running the app again, we'll get data from both API calls.
In case of an error in promises in parallel, we won't get any result, and the error will be shown. Again, we have a wrong URL for posts in the APIFetchHandler.swift file.
Upon running the app, we'll get the error shown below.
Testing the App
We'll do a simple XCTest in our app for the post API URL. So, we've moved the post API URL outside and made it public in the ViewController.swift file.
We've also removed it from the fetchAPIData() and fetchDataWithPromise() functions while using the new reference of postUrl in both methods.
First, go to the PromiseKitDemoTests.swift file inside the PromiseKitDemoTests folder. Here, we'll create an instance of APIFetchHandler. After that, use the XCTAssertEqual function and check whether the URL is equal to https://jsonplaceholder.typicode.com/posts.
Now, run this test by clicking on the play button beside the testExample(). Our test passed, and we're also getting confirmation in the console.
What We’ve Learned
In this post, we’ve talked about promises for doing asynchronous API calls. We can do the same in Swift using the open-source package of PromiseKit.
We created a simple project in XCode, then hit the JSON placeholder API endpoint.
First, we saw an example with a callback. After that, we converted that to use promises.
Next, we saw a complex example with parallel promises. We also wrote simple test cases using XCTest to test just a string.
But to test the API calls is a difficult task, as it requires complex mocking logic. Instead of that, we can use Waldo to perform such tests. You’re only required to provide the APK or IPA file. Waldo automatically generates test cases and emails you a nice report.
Check out this link to learn more about SwiftUI.
Automated E2E tests for your mobile app
Get true E2E testing in minutes, not months.