This method will use the countries API to retrieve the full list of countries.
We will use the Fetch API to retrieve the data, as well as our io-ts validators to make sure that the data matches our expectations at runtime.
Finally, we'll use async and await to write synchronous-looking code, even if everything is actually asynchronous.
First, add the following utility methods to the class.
There is one to check the status of the response:
async checkResponseStatus(response: Response): Promise<Response> { if(!response) { throw new Error("A response must be provided!"); } if (response.status >= 200 && response.status < 300) { return Promise.resolve(response); } else { return Promise.reject(new Error(response.statusText)); } }
A second one is to get the JSON content out of the response:
async getJsonContent(response: Response): Promise<unknown> { if(!response) { throw new Error("A response must be provided!"); } let jsonContent: unknown = undefined; try { jsonContent = await response.json(); } catch (error) { console.error("Failed to parse the response as JSON: ",
error); throw new Error(`Could not parse the response body as JSON.
Error: ${error.message}`); } return jsonContent; }
We'll use the checkResponseStatus method to check that API responses are successful (that is, that they at least return a 2xx HTTP status code).
The getJsonContent method uses await to wait until the conversion of the response body to JSON is completed. Since this operation might fail, we've done this within a try-catch block in order to be able to return a meaningful response back to the calling code.
In the catch block, we've used Promise.reject(...) to return a custom error message back, but we could also have simply used throw new Error(...) to achieve the same result.
Now, implement the getAllCountries method as follows:
async getAllCountries(): Promise<Country[]> { // <1> const response: Response = await fetch(${this.
countriesApiBaseUrl}?${WorldBankApiV2Params.FORMAT}=
${WorldBankApiV2Formats.JSON}&
${WorldBankApiV2Params.PER_PAGE}=320); // <2> const checkedResponse: Response = await
this.checkResponseStatus(response); // <3> let jsonContent: unknown = await
this.getJsonContent(checkedResponse); const validationResult = worldBankApiV2CountryResponseValidator.
decode(jsonContent); // <4>
// throw an error if validation fails ThrowReporter.report(validationResult); // <5>
console.log("Response received and validated"); // from here on, we know that the validation has passed const countries = (validationResult.value as
WorldBankApiV2CountryResponse)[1]; // <6> console.log(`Found ${countries.length} countries`); let retVal: Country[] = countries.map(country => // <7> new Country( country.name, country.id, country.iso2Code, country.capitalCity, country.longitude, country.latitude ) ); return retVal; // <8> }
Let's go through the code together:
- We've marked the function as async. This is what allows us to use await and write synchronous-looking code.
- We've used await to wait until fetch returns with the response of our API call.
- We've used await again to wait until the response has been checked by our utility function.
- We've used our io-ts validator (that is, worldBankApiV2CountryResponseValidator) to check the validity of the response.
- We've called ThrowReporter.report(...) to make sure that we throw an error if validation fails. We could also have checked the validity ourselves using if(validationResult.isLeft) { ... }, but ThrowReporter is clearer.
- We've cast the validation result as WorldBankApiV2CountryResponse, which is safe at this point (we've gone through the validation successfully!). Note that since the World Bank APIs always return data in the form [{pagination_data},{data}], we've used [1] to simply get the data.
- We've then mapped our array to a new array of Country class instances.
- Finally, we've returned the resulting array directly.
Notice that we simply return our array. In this case, we don't need to use Promise.resolve. Since the function is marked as async, the returned value will be wrapped in Promise in any case.
Doesn't this code look and feel like synchronous code?