Unhappy flow
Thus far we've focused on what's called the "happy flow", which means the course that's followed by the user without encountering any problems. In real life many things could go wrong though, so we need to think of the unhappy flow as well!
Inspecting the response
For the TaskFlow API we might have a service for retrieving specific time entries which looks like this:
export async function getTimeEntries():Promise<Types.TimeEntry[]> {
const response = await fetch("http://localhost:3004/time-entries", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
return response.json();
}
Now what happens if there are no time entries yet? As part of the REST convention the API will return a "404 Not Found" error status code. We'll probably end up with a console error and more importantly: a confused user.
So let's start with inspecting the response:
export async function getTimeEntries():Promise<Types.TimeEntry[]> {
const response = await fetch("http://localhost:3004/time-entries", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
console.log(response.status);
return response.json();
}
If everything works and time entries exist, calling this function results in response.status
being set with a value of 200
, which is the HTTP status code for OK
. Otherwise, it could return 404
or one of the other HTTP status codes. Now in case of a 404
, we'd like to throw an error.
Defining a custom error
By default errors are thrown by the Error
class in JavaScript/TypeScript. When creating a custom error we can simply extend this class. That way all functionality of the error class is preserved.
Let's create an error that we can use if the API can't find what we're looking for:
class NotFoundError extends Error {
constructor(message) {
super(message);
this.name = "NotFoundError";
}
}
With the code above we've created a copy of the default error and renamed it to NotFoundError
. We can now have all our API services throw this error if there are no results.
Throwing the error
In order to implement the error we need to head back to our API service. Import the custom error and utilize response.status
to determine whether the error needs to be thrown:
export async function getTimeEntries():Promise<Types.TimeEntry[]> {
try {
const response = await fetch("http://localhost:3004/time-entries", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (response.status === 404) {
throw new NotFoundError();
}
return response.json();
} catch (error) {
return console.error(error);
}
}
As you can see, we're using JavaScript's try...catch
statement here. The code in the try
block is executed first, and if it throws an exception (in this case our NotFoundError
), the code in the catch
block will be executed.
Handling the error
With these minor adjustments we've created the foundations of a really neat flow handling, regardless whether it's the happy or unhappy flow.
In the happy flow we'd implement the API service as such:
const timeEntries = await getTimeEntries();
console.log('Time entries: ', timeEntries);
Now we're throwing a custom error of which we can use the instanceof
operator to determine the response type. If it's an instance of NotFoundError
we know the API wasn't able to find the time entry, otherwise we can proceed as before.
const timeEntries = await getTimeEntries();
if (timeEntries instanceof NotFoundError) {
console.log('Not found!');
return;
}
console.log('Time entries: ', timeEntries);
By returning we stop the rest of the function from being executed. That way we can perform the feedback from the unhappy flow in the if-statement and then prevent the rest of the function, which represents the happy flow, from being executed.