I am trying to make a function return two possible types of data. The first type one it could return is of string the second type it could return is of fs.WriteStream
In the function this is based on a parameter you pass to it called stream?: boolean
As you can see in the picture, VSCode correctly shows this behaviour of this function
However, somehow the intellisense recognizes it only as it being a string while the actual type could also be of fs.WriteStream
But if I force it to be of type fs.WriteSteam using
const tt = await getFromUrl(s, true) as fs.WriteStream;
It works fine
What is going here? How can I make this union type work correctly without forcing it to be of type?
Here is the full code incase you wan’t to replicate it
import { existsSync, readFileSync } from 'fs'
import https from 'https'
import fs from 'fs'
import crypto from 'crypto'
/**
* Converts given source from a SRT format to a WebVTT format
*
* Srt must be one of type
* - Path to subtitle file (only for NodeJS environments)
* - URL to a subtitle file (for browser and NodeJS)
* - SRT text content (for browser and NodeJS)
*
* @param srt { string }
* @returns string
* @example
* convert(`./path/to/subtitle.srt`) // only for nodejs
* convert(`https://example.com/english.srt`) // works for both browser and nodejs
* convert(`1
00:00:00,207 --> 00:00:05,520
Amerikaanse Ministerie
van Magie.
2
00:00:06,625 --> 00:00:09,034
New York, 1927
3
00:00:10,878 --> 00:00:15,547
Je zal blij zijn van hem af te zijn
neem ik aan?`) // works for both browser and nodejs
*/
export async function convert(srt: string) {
const s = srt.trim()
const isUri = s.startsWith("http://") || s.startsWith("https://")
const isPath = /^([a-z]:)?([.\/]+)?((\\|\/|\\\\)?[a-z0-9\s_@\-^!#$%&+={}\[\]]+)+\.srt$/i.test(s)
if(isPath && existsSync(s)) {
return srtToVtt(readFileSync(s, 'utf8'));
}
if(isUri) {
const tt = await getFromUrl(s);
console.log(tt.path);
}
// Must be the contents of an srt file of we get here
return ''
}
/**
* Fetches text content from srt file over web
*
* @param url
* @param stream - return result as a writeable stream or just the data (default true)
*/
export async function getFromUrl(srt: string, stream = true): Promise<string | fs.WriteStream> {
return new Promise(resolve => {
https.get(srt, response => {
if(stream) {
const str = fs.createWriteStream(crypto.randomBytes(16).toString("hex") + '.srt')
response.pipe(str).on('finish', () => {
str.end().close();
return resolve(str)
})
} else {
let srtFile = '';
response.on("data", function(chunk) {
srtFile += chunk;
});
response.on('end', () => {
console.log('test')
return resolve(srtFile)
})
}
})
})
}
>Solution :
Why you get this error:
The type of the tt variable is string | fs.WriteStream. Since you don’t know which of the two types you have in that variable, any method or property that you want to access has to be present on both types.
As you saw, you can fix that by narrowing the variable’s type to just one side or the other of that union. You can do that with a manual cast (your as fs.WriteStream) or TypeScript can infer the correct type if you use control flow to check:
if (typeof tt === "string") {
// tt has type string in this block.
return;
}
// tt has type fs.WriteStream from here forward.
Consider an Overload:
This function could likely benefit from an overload signature, so that the ambiguity around which part of the union you get is removed. This way, TypeScript can infer the correct return type based on the way in which you invoke the function.
async function getFromUrl(srt: string, stream?: false): Promise<string>;
async function getFromUrl(srt: string, stream: true): Promise<fs.WriteStream>;
async function getFromUrl(srt: string, stream?: boolean): Promise<string | fs.WriteStream> {
return ""
}
const str1 = await getFromUrl("") // string
const str2 = await getFromUrl("", false) // string
const stream = await getFromUrl("", true) // fs.WriteStream



