Skip to content

to wrap or not to wrap? #63

@wmathes

Description

@wmathes

Now that the module structure is finally working like a charm, i'm playing around with actually using this with the shop service module. Having the native fetch response with typing still feels very clunky and i'm thinking about if we should wrap it or not...

Here's a quick example to illustrate the idea and the problem.

Creating a service package will basically consist of a class which extends Service like so

import {IResponse, Service} from "@crazy-factory/cf-service-client";

export interface IMyEndpointData {
  value: string;
}

export class MyService extends Service {
  public getPost(id: number): Promise<IResponse<IMyEndpointData>> {
    return this.client.process({
      url: `/endpoint/${id}`
    });
  }
}

So this is nice and fine. Easy to read and generate. It will pass through the Stack, reach the Fetch Middleware and return Promise containing the typed FetchResponse. All good?

Not quite... because of the nature of the fetch response object which needs a lot of caretaking to play nicely... like so

// get the service from somewhere... container, global, singleton, factory, blah
var myService = getMyServiceInstance();

// make the request, it will return a promise
myService.getPost(15).then((response) => {
    // figure out if it worked and if we have json data
    if (response.ok && response.headers.get("Content-Type").indexOf("application/json") != -1) {
        // get a promise that will get the json data
        response.json().then((data) => {
            // do something with data
            alert(`the cow says ${data.value}`);
        });
    }
});

This looks ok on a first glance, but feels kind of clunky and i can already feel the pain of having to write this code block hundreds of times. Sadly there are some good reasons behind it.

Pros

  • no double standards all requests are treated equally. the promise should never reject. Using the ok- and status-properties is clean and easy.
  • low performance impact as long as you don't call json() the stream will never be parsed. Parsing of unnecessary information can be skipped.

Cons

  • ContentType comparison is mandatory. Because json() will try to parse the stream as json no matter what. This will result in Errors, but could by accident actually lead to parsable values (e.g. a number or an empty string).
  • Duplicate code and validation logic
  • Any single function call that uses the body (this includes json()) will break all future calls to body.

So how can we solve this without loosing some of the benefits of this approach?

Initially we had the FetchResponse class to handle parsing of the FetchResponse and i think we should go for this again. There should be some changes made though, which may require a few changes to the Client class (mostly to it's typings).

  • implement conversion as Middleware?
  • return a simple immutable object?
  • it should give access to the original fetch response?
  • it should only parse/fetch json when actually got?
  • json data should be accessible as a get field?
  • json data should be accessible multiple times and be immutable?
  • different return types for data and blob endpoints?
  • what do we actually need?
    request? response?
    data, text, blob?
    hasData, hasText, hasBlob?
    ok, status, statusText?
    headers?
    any additional fields?
  • how to handle non-data endpoints?

How to handle middleware adding additional values to the request?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions