Alexander Mills
Alexander Mills

Reputation: 100370

Use generics to specify type of return value

I have this method on an Angular service:

 getFromSyncStorage(key: string): Promise<Object | LastErrorType> {
    return new Promise(function (resolve, reject) {
      chrome.storage.sync.get(key, function (v: Object) {
        if (chrome.runtime.lastError) {
          return reject(chrome.runtime.lastError);
        }
        resolve(v && v[key]);
      });
    });
  }

note that LastErrorType is

export type LastErrorType = typeof chrome.runtime.lastError;

here is the problem, sometimes I pass a key, and I know the "return" type will be an array, for example:

getAllRunHistory() {
    return this.cds.getFromSyncStorage('my-special-key');
}

but if I do this:

getAllRunHistory() : Promise<Array<any>>{
    return this.cds.getFromSyncStorage('my-special-key');
}

tsc will complain and say that the return type is not assignable to Promise<Array<any>>.

how can I use generics, so that this general-purpose getFromSyncStorage method can be typed properly?

Upvotes: 0

Views: 333

Answers (2)

Alexander Mills
Alexander Mills

Reputation: 100370

Looks like generics is the right thing to use. We change the original method to this:

 getFromSyncStorage<T>(key: string): Promise<T> {
    return new Promise(function (resolve, reject) {
      chrome.storage.sync.get(key, function (v) {
        if (chrome.runtime.lastError) {
          return reject(chrome.runtime.lastError);
        }
        resolve(v && v[key]);
      });
    });
  }

then we use it like so:

this.getFromLocalStorage<Array<MyType>>(mds.key).then(...)

one caveat though, I don't really know how to specify the error type for the promise, which sucks, but I think it's a deficiency with the Promise TypeScript typings :(

Upvotes: 0

jcalz
jcalz

Reputation: 330161

You can do this with generics. Create a type representing the mapping from key to value type. In your case, it's something like:

type SyncStorageMap = {
    "my-special-key": Array<any>,
    [k: string]: Object // everything else
}

Then, type your method like this:

getFromSyncStorage<K extends keyof SyncStorageMap>(
  key: K
): Promise<SyncStorageMap[K] | LastErrorType> {
   // impl
}

Note that you might have to use type assertions in the implementation of that method if the compiler can't verify that "my-special-key" corresponds to Array<any>.

Then this code:

getAllRunHistory() {
    return this.cds.getFromSyncStorage('my-special-key');
}

will be known to return Promise<Array<any> | LastErrorType>. That's probably what you want unless you're sure that you will not get a LastErrorType, in which case you can do something like this instead:

type SyncStorageMap = {
    "my-special-key": Array<any>, // no error
    [k: string]: Object | LastErrorType // maybe error
}

getFromSyncStorage<K extends keyof SyncStorageMap>(
  key: K
): Promise<SyncStorageMap[K]> {
   // impl
}

Hope that helps; good luck!

Upvotes: 1

Related Questions