1

Here's a possibly very simple question for the ones that have read the TypeScript handbook:

Consider the following interface: (it's from the simple-git library which I'm trying to use).

export interface SimpleGitTaskCallback<T = string, E extends GitError = GitError> {
    (err: null, data: T): void;
    (err: E): void;
}

I have a function which has the following signature:

checkIsRepo(action?: types.CheckRepoActions, callback?: types.SimpleGitTaskCallback<boolean>): Response<boolean>;

The second argument presents a problem for me.

If I call the function like this (just like the first signature in the interface requires me to):

    git.checkIsRepo(CheckRepoActions.IS_REPO_ROOT, (error: null, isRepo: boolean): void => {
  console.log(error);
  console.log(isRepo);
});

I get:

Argument of type '(error: null, isRepo: boolean) => void' is not assignable to parameter of type 'SimpleGitTaskCallback<boolean, GitError>'.

Passing a function that matches the second signature in the interface does work:

git.checkIsRepo(CheckRepoActions.IS_REPO_ROOT, (isRepo: any): void => {
  console.log(isRepo);
});

It works, but it prints null because what I'm getting in isRepo is actually the null error (since the command completes successfully.

But what I want is to understand how to pass a function satisfying the first signature.

Worth noting:

Doing this with promises works just fine:

    git
  .checkIsRepo(CheckRepoActions.IS_REPO_ROOT)
  .then((isRepo: boolean): void => {
    console.log(isRepo);
  })
  .catch((err: GitError): void => {
    console.log(err);
  });
3
  • IT would be greate if you provide reproducable example Commented Jan 12, 2021 at 13:10
  • 1
    Your second callback example that "works" seems to be misleading. It's actually satisfying both of the function types. Check this playground Commented Jan 12, 2021 at 14:11
  • @captain-yossarian yeah, true, but I figured it's a language question that somebody experienced in TS would catch fast. As it turns out, it was actually just me not understanding a vital concept which is that interfaces are intersection of types. That, plus the fact that I'm even more suffocated for time than most coders (temporary rough life situation). Commented Jan 12, 2021 at 16:35

1 Answer 1

2

When you define an interface that has multiple call signatures, anything you assign to it must satisfy all of those call signatures. Not just one of those call signatures. That would be suitable for a union type.

export interface Callback<T = string, E extends Error = Error> {
    (err: null, data: T): void;
    (err: E): void;
}

// doesn't work
const f: Callback = (error: Error): void => {};
//    ^ `null` is not assignable to type `Error`

// works
const f1: Callback = (a: any): void => {};
//    ^ `(a: any): void` satisfies both `(err: null, data: T): void` and `(err: E): void`

You might ask, how does the second example satisfy both? It clearly does not have data and hence is only satisfying the first function type.

Well, let's compress the overloaded interface type to see what the function being described by the interface actually looks like-

(err: E | null, data?: T)

That should be clearer. Now it's obvious why (err: any) => void satisfies both. Because the data param is actually just optional and is never checked, and the err param can be anything.

So, what's a valid and practical function you can assign to Callback? This should do-

// Works just fine - check if error is an actual error or null inside the body
const correctF: Callback<boolean> = (err: Error | null, data?: boolean): void => {};

Check this out on playground

The type system cannot know if the callback will be called with the first signature or the second signature. So you need to provide a callback that can handle both.

When you use .then, and .catch - the type system explicitly knows which callback to use for the error case, and which to use for the non error case. So the promise chain can use a different callback type that doesn't need to be overloaded. Which is probably what simple-git does as well.

Sign up to request clarification or add additional context in comments.

1 Comment

Chase, thanks for providing one of the best and kindest answers I've ever received :). Thank you for the time you put in it. Very clear and you covered everything, exhaustively. I'd triple-check this answer if I could :D

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.