简体   繁体   中英

Specialized overload/implementation of a generic function with a default parameter value

I have a generic function with a required transformer generic function parameter and a generic return type T .

I want to add a "default value" for that transformer parameter that has a concrete type ( ArrayBuffer ).

I think I've written the overload signatures correct, but my implementation does not compile.

export async function downloadWithCache(
  url: string
): Promise<ArrayBuffer>;
export async function downloadWithCache<T>(
  url: string,
  transformer: (buffer: ArrayBuffer) => T
): Promise<T>;
export async function downloadWithCache<T>(
  url: string,
  transformer?: (buffer: ArrayBuffer) => T
) {
  return someImplementation(url, transformer || (x => x));
}
Argument of type '((buffer: ArrayBuffer) => T) | ((x: ArrayBuffer) => ArrayBuffer)' is not assignable to parameter of type '(buffer: ArrayBuffer) => T'.
  Type '(x: ArrayBuffer) => ArrayBuffer' is not assignable to type '(buffer: ArrayBuffer) => T'.
    Type 'ArrayBuffer' is not assignable to type 'T'.
      'T' could be instantiated with an arbitrary type which could be unrelated to 'ArrayBuffer'.ts(2345)

PS I think that someImplementation is irrelevant, but I was asked to post it:

const httpGetDataWithCache = async <T>(
  url: string,
  transformer: (buffer: ArrayBuffer) => T,
  cacheName: string = "cache",
  updateIfInCache: boolean = false
): Promise<T> => {
  const cache = await caches.open(cacheName);
  let response = await cache.match(url);
  let needToUpdateCache = false;
  if (response === undefined || updateIfInCache) {
    try {
      const newResponse = await fetch(url);
      if (!newResponse.ok) {
        throw new Error(
          `Network response was not OK: ${newResponse.status}: ${newResponse.statusText}`
        );
      }
      response = newResponse;
      needToUpdateCache = true;
    } catch (err) {
      if (response === undefined) {
        throw err;
      }
    }
  }
  // Preventing TypeError: Failed to execute 'put' on 'Cache': Response body is already used
  const responseForCaching = response.clone();
  // Need to verify that the transformer executes with error before putting data in cache.
  const result = transformer(await response.arrayBuffer());
  if (needToUpdateCache) {
    await cache.put(url, responseForCaching);
  }
  return result;
};

const someImplementation = httpGetDataWithCache;

You need to change function type signature:

declare const someImplementation: <T>(
    url: string,
    transformer: (buffer: ArrayBuffer) => T,
    cacheName?: string,
    updateIfInCache?: boolean
) => Promise<T>

export async function downloadWithCache(
    url: string
): Promise<ArrayBuffer>;
export async function downloadWithCache<T>(
    url: string,
    transformer: (buffer: ArrayBuffer) => T
): Promise<T>;

export async function downloadWithCache(
    url: string,
    transformer?: (buffer: ArrayBuffer) => ArrayBuffer // CHANGE IS HERE
) {
    return someImplementation(url, transformer || (x => x));
}

downloadWithCache('a', (x) => x.byteLength) // ok, Promise<number>

Playground

It works because function overloads are bivariant .

See simplified example:

declare let a: (buffer: ArrayBuffer) => ArrayBuffer
declare let b: <T, >(buffer: ArrayBuffer) => T

a = b // ok
b = a // error

At least one operation should be ok to make overloadings work.

See docs

The implementation signature must also be compatible with the overload signatures.

In our case, implementation is compatible with overload signature, but not vice versa

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM