Home > front end >  How to deal with multiple possible return types in typescript?
How to deal with multiple possible return types in typescript?

Time:10-31

I have looked into this thread Get local IP address in Node.js, where I want to implement a piece of code:

import net from 'net';

const getNetworkIP = (callback: any) => {
  var socket = net.createConnection(80, 'www.google.com');
  socket.on('connect', function () {
    const sa = socket.address();
    callback(undefined, sa.address);
    socket.end();
  });
  socket.on('error', function (e) {
    callback(e, 'error');
  });
};

I can not call sa.address because the socket.address() will return either {} or net.AddressInfo. How to deal with this common code scenario in typescript? How can I access the address attribute safely?

CodePudding user response:

This is weird since the docs don't mention the possibility of returning an empty object.

However, blaming the @types/node declaration files leads to this PR with the comment

-    address(): AddressInfo;
     address(): AddressInfo | {};

In case of windows pipes or unix domain sockets {} is returned (server returns the path as string).

So if you're certain that this doesn't apply to your code, you can use a type assertion:

const sa = socket.address() as net.AddressInfo;
callback(undefined, sa.address);

but if you want to do it properly and detect the occurrence you'd write

const sa = socket.address();
if ('address' in sa) {
  callback(undefined, sa.address);
} else {
  callback(new Error(`Expected an AddressInfo object, got ${sa}`));
}

CodePudding user response:

You can validate the shape of the returned value at runtime with a type guard function. See comments below:

Ref: Class: net.Socket | Net | Node.js v18.12.0 Documentation

TS Playground

import {default as net, type AddressInfo, type Socket} from 'node:net';

// The type of your callback signature looks like it's one of these:
type NetworkIPCallback = {
  /** For handling an error */
  (error: Error, msg: 'error'): void;
  /** For handling a discovered address */
  (_error: undefined, address: string): void;
};

/** Type guard function */
function isAddressInfo (info: ReturnType<Socket['address']>): info is AddressInfo {
  return (
    typeof info === 'object'
    && typeof (info as AddressInfo)?.address === 'string'
    && typeof (info as AddressInfo)?.family === 'string'
    && typeof (info as AddressInfo)?.port === 'number'
  );
}

const getNetworkIP = (callback: NetworkIPCallback) => {
  const socket = net.createConnection(80, 'www.google.com');

  socket.on('connect', () => {
    const info = socket.address();

    if (isAddressInfo(info)) callback(undefined, info.address);
    else callback(new Error('Socket address not found'), 'error');

    socket.end();
  });

  socket.on('error', (error) => callback(error, 'error'));
};

And if you only care about the object having an address property of type string, then you could use a simplified type guard:

TS Playground

function hasStringAddress <T>(value: T): value is T & { address: string } {
  return (
    typeof value === 'object'
    && typeof (value as any)?.address === 'string'
  );
}
  • Related