Don’t be CRUD: with async & promise
There are plenty of libraries out there to handle HTTP calls, states, stores, and ORM. Most of them are designed for bigger challenges than ours. Some solve historical issues. So before choosing the best framework, we listed our requirements:
- Hide the network mess
- Fetch Interceptor
- Hidden security layer: headers, CSRF token.
- Supporting different environments.
- Localhost environment.
- Error handling.
- Send/receive only model data.
- Validations
- Version management.
- Cancelation.
- CRUD and more.
- Async & promise simultaneity.
While writing the requirements of the Model layer of our new app, I couldn’t resist accepting the challenge…
The goal was to define the objects like this: (later on changing it to TypeScript and changing the _obj to abstract)
class Joke extends Fetch{ _url = "https://sv443.net/jokeapi/v2/joke/Programming"; _updatedValue = "modifyDate"; _obj = { id: 0, modifyDate: new Date(), category: "", type: "", text: "", flags: { religious: false, political: false }, error: false } }
Use it this way:
const joke = new Joke(); joke.text = "Why can't a programmer tell a joke? Since he can't GET it"; await joke.save(); // [POST] "v1/jokes/" // send {text:"why..."} // receive {id: 123, text:"why..."} ... ... await joke.hasChanged(); if(joke._oldVersion){ await joke.get(); // [GET] "v1/jokes/123" // receive {id: 123, text:"why..."} }else{ joke.text = "Why can't a programmer without this post tell a joke? Since he can't GET it"; await joke.save(); // [PUT] "v1/jokes/123" // send {id: 123, text:"why..."} // receive {id: 123, text:"why..."} }
And at the same time:
joke.promise(jole.hasChanged).then(...
joke.promise(joke.get).then(...
joke.promise(joke.save).then(...
I started to write this post as a step by step development guide, but since I’m the only reader, and this will take to much family time for the un-educating process, I’ll just link it and explain the main points.
Later update: after building this, (removing the async) and rewriting to TypeScript, I found this article:
The fetch API is a native JavaScript function that we can use to interact with web services. How can we use fetch with async and await? and how can we use this with TypeScript to get a strongly-typed response? Let’s find out …
Carl Rippon: Fetch with async & await and TypeScript
fetchApi class
The first challenge was to wrap the fetch call, and support both cancellations of specific call, and cancel all requests. It created a weird mixture of promise and async. I prefer using async, but only promise calls can be canceled.
The cancelIt method will cancel the specific call. The static q keeps the calls list to allow cancel all. If this code gets to production, it will be good practice to remove resolved calls from the cancelation list.
One more feature that we will add later is the option to cancel by toke, or type of calls. So you cancel a group of calls. But since it’s just a POC / #NotFancyAppChallenge, I left it for the one day that never comes.
The fetch class
This class has the CRUD, and one fetch method for other calls that can be created according to the different model classes. The methods that get data like get and save call a different helper that copies only the predefined structure from the object to the ASI call, and backward. One more generic call is the hadCahnged method that tracks changes from the server.
Static util class
Currently with only one method to copy only the predefined object data. Both to get it from the object to sent to the server, And to copy the server response into the object.
And to see it live…
Unit tests
The last section in this post is the Jest testing layer, which I will not post here, just save fore basic implementation…
import {Fetch} from '../fetch'; import { enableFetchMocks } from 'jest-fetch-mock'; enableFetchMocks(); describe('the fetch testing', () => { fetchMock.mockIf(/^http.*$/, req => { if (req.method == "GET") { return { code: 200, body: {joke:'some mock'}, headers: { "X-Some-Response-Header": "Some header value" }}} return { status: 404, body: "Not Found" } }) }) test('System abort', async () => { const joke = new Joke(); await joke.get(); expect(res.code).toEqual(200); }); ... ... });
Is it perfect? not yet. Will ever be, probably not. Will it be used? I’ll keep you posted.