I verified in the console that coordinator.setupPromise
is a Promise, but when I run await coordinator.setupPromise
, the code never gets to the print statement after the await and instead of waiting it just exits immediately. Example console output:
$ tsx main.ts
setup
await setup Promise { <pending> }
Heres my code:
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
import { getPortFree, sleep } from "../utils";
import 'child_process'
import fetch, { RequestInit } from "node-fetch";
import { SocksProxyAgent } from "socks-proxy-agent";
class TorInstance {
socksPort: number;
controlPort: number;
dataDir: string = `./datadirs/${Math.random().toString(36).slice(2)}`;
torProcess: ChildProcessWithoutNullStreams;
agent;
setupRes;
setupPromise = new Promise(res => { this.setupRes = res });
constructor(socksPort?, controlPort?) {
this.setup();
}
private async setup(socksPort?, controlPort?) {
let port1 = await getPortFree();
let port2 = await getPortFree();
this.socksPort = socksPort ?? port1.port
this.controlPort = controlPort ?? port2.port
port1.server.close();
port2.server.close();
// tor --SocksPort 11111 --ControlPort 11112 --DataDirectory test1
this.torProcess = spawn(`tor`, ['--SocksPort', this.socksPort, '--ControlPort', this.controlPort, '--DataDirectory', this.dataDir].map(String));
this.torProcess.stdout.setEncoding('utf8');
this.torProcess.stdout.on('data', (data) => {
if (data.toString().includes(`[notice] Bootstrapped 100% (done): Done`)) { this.setupRes() }
})
this.agent = new SocksProxyAgent(`socks5h://127.0.0.1:${this.socksPort}`);
}
public fetch(url: string, options?: RequestInit) {
return fetch(url, {
...options,
agent: this.agent
})
}
public dispose() {
this.torProcess.kill();
}
}
class TorCoordinator {
instances: TorInstance[] = [];
currentIndex = 0;
setupRes;
setupPromise = new Promise(res => { this.setupRes = res; })
constructor(num: number) {
this.createInstances(num);
}
fetch(url: string, options?: RequestInit) {
return this.instances[this.currentIndex++].fetch(url, options);
}
createInstances(num) {
let newInstances: TorInstance[] = [];
for (let i = 0; i < num; i++) {
let newInstance = new TorInstance();
newInstances.push(newInstance);
newInstance.setupPromise.then(() => {
console.log('setup!!')
this.instances.push(newInstance)
this.setupRes();
})
// todo monitor console output for [notice] Bootstrapped 100% (done): Done, and only add to main list then
}
}
}
async function test() {
console.log('setup')
let coordinator = new TorCoordinator(10);
console.log('await setup',coordinator.setupPromise)
await coordinator.setupPromise;
console.log('done await')
coordinator.fetch('https://api.ipify.org?format=json').then(res => res.text().then(console.log))
coordinator.fetch('https://api.ipify.org?format=json').then(res => res.text().then(console.log))
await sleep(10000)
}
test();
And utils.ts is:
import { SocksProxyAgent } from "socks-proxy-agent"
export function sleep(m) { return new Promise(r => setTimeout(r, m)) }
export const torAgent = new SocksProxyAgent('socks5h://127.0.0.1:9050');
import { Readable } from 'stream';
export async function streamToBuffer(stream) {
const chunks = [];
for await (const chunk of stream) {
chunks.push(chunk); // Collect each Uint8Array chunk
}
// Concatenate all chunks into a single Buffer
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
const buffer = Buffer.alloc(totalLength);
let position = 0;
for (const chunk of chunks) {
buffer.set(chunk, position);
position += chunk.length;
}
return buffer;
}
export async function streamToString(stream: ReadableStream) {
const reader = stream.getReader()
let html = ''
while (true) {
const { value, done } = await reader.read()
if (value) {
html += new TextDecoder().decode(value)
}
if (done) {
return html
}
}
}
export async function streamToStringOld(stream: ReadableStream<Uint8Array<ArrayBufferLike>>) {
const chunks: any = [];
// Use async iteration to read from the stream
for await (const chunk of stream) {
chunks.push(chunk); // Collect each Uint8Array chunk
}
// Concatenate all chunks into a single Uint8Array
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
const resultArray = new Uint8Array(totalLength);
let position = 0;
for (const chunk of chunks) {
resultArray.set(chunk, position);
position += chunk.length;
}
// Convert the Uint8Array to a string
return new TextDecoder().decode(resultArray);
}
import net from "net"
export async function getPortFree():Promise<{port:number,server:net.Server}> {
return new Promise( res => {
const srv = net.createServer();
srv.listen(0, () => {
// @ts-ignore
const port = srv.address().port
res({port,server:srv});
});
})
}
I tried awaiting a promise that I had constructed earlier, but instead of awaiting for passing to the next line, the code just exists with no error thrown.