Welcome to part 5 of my Practical Promises series! Today, we’re going to use what we’ve learned to build a better client-side API for a web API.
[more]
If you are just joining us, here is what you missed:
In part 1, we talked about what promises are and what they can be used for.
In part 2, we started looking at how we can create promises.
Then in part 3, we saw how each call to then actually makes a new promise, and that those promises can be chained together.
In part 4, we learned how to combine promise chaining with the creation of new promises in order to simplify complex async workflows.
The Challenge
Let’s say we have a typical REST Web API for interacting with Widgets. You can get widgets, create new ones, etc etc. A sample GET request might look like this:
GET http://our.api/Widgets
[
{"id": "1", "name": "Widget 1", ...},
{"id": "2", "name": "Widget 2", ...},
{"id": "3", "name": "Widget 3", ...},
...
]
Let’s also pretend that we want to provide developers with a nice client-side API they can use to interact with this web API.
One option would be to create a very low-level API that does little more than encapsulate the URLs for our API.
Our raw code HTTP request code might look something like this:
function getWidget(id) {
//Note: you don't really need a variable here, but I wanted to make it obvious what we're handing back.
const promise = request(`http://our/api/widget/${id}`);
return promise;
}
The request
function actually returns a response object, which might look something like this:
{
"statusCode": "200",
"data": "{\"id\":\"1\", \"name\":\"widget 1\"}"
}
That means that anyone that calls our getWidget
function will need to unwrap the response to actually use it, like so:
getWidget(1).then(response => {
$ctrl.widget = JSON.parse(response.data);
});
That means our callers are now coupled to the underlying request
function and how it returns data. That’s not ideal.
Using Promises to Unwrap Objects
We can use what we’ve learned about promise chaining to encapsulate request
logic and expose a simpler result.
Instead of returning the response
object directly, we’ll expose an API that will return just the widget to callers. We can leverage promise chaining to do exactly that:
function getWidget(id) {
const promise = request(`http://our/api/widget/${id}`);
return promise.then(response => JSON.parse(response.data))
}
Remember: Calling
promise.then
actually creates a new promise, and that promise will be resolved with whatever we return from ourthen
callback. In this case, that is going to be the result ofJSON.parse
.
Now anyone that uses our getWidget
function will receive the actual widget, and not the underlying response:
getWidget(1).then(widget => {
$ctrl.widget = widget;
});
This encapsulation will also simplify everyone’s lives when its time to put specs around components that utilize our API. Instead of mocking out a complex response
object, mocks can be created to return our Widget
object directly.
Up Next
We’re almost finished with this run through promises, but there’s one last thing I want to show you: how to use the new ES7 async/await features to simplify things even further! Stay tuned!