Promise-based HTTP abstraction layer
First thing, you're right about HttpRequest and the oddness of having to pass manually body and url .

To improve the code, I would suggest to chose a programming paradigm, and to stick to it:

example in OOP:

class HttpRequest&lt;TResult&gt; {
    constructor(private method: HttpMethod, private url: string, private body?: any) {}
    request():Promise&lt;TResult&gt; {
        return request(this.method, this.url, this.body); 
    }
};

const req1  = new HttpRequest&lt;FirstResponse&gt;('POST', `http://example.com/one`, {
    id: 9,
    data: [`hi`, `there`]
  });
const req2 = new HttpRequest&lt;SecondResponse&gt;('GET', `http://example.com/one`);

// you now just have to call req1.request() for example , without parameters


in more FP friendly way of doing thing:

function my_request&lt;T&gt;(httpReq: HttpRequest) {
    return  (): Promise&lt;T&gt; =&gt; request(httpReq.method, httpReq.ufl, httpReq.body)
}

const req1  = my_request&lt;FirstResponse&gt;({method:'POST', url:`http://example.com/one`, body:{
    id: 9,
    data: [`hi`, `there`]
});
const req2 = my_request&lt;SecondResponse&gt;({method:'GET', url:`http://example.com/one`});


// you now just have to call req1() for example , without parameters


Now, we can improve the typing of waitAll with mapped types, for example for the OOP version (it's equivalent for the FP one):


function waitAll&lt;T extends any[]&gt;(...reqs: {[K in keyof T]: HttpRequest&lt;T[K]&gt;}): Promise&lt;T&gt; {
  // the any here is mandatory as Promise.all doesn't support (yet) the above signature
  return Promise.all(reqs.map(req =&gt; req.request())) as any;
}

// result type will be a [FirstResponse, SecondResponse]
const result = waitAll(req1, req2)


Now, you should ask yourself if you're not "over-abstracting". A code like that:

const req1 = request('POST', `http://example.com/one`, {id: 9});
const req2 = request('GET', `http://example.com/one`);

const [r1, r2] = await Promise.all(req1, req2);

const finalResponse = await request('POST', `http://example.com/three`, body);


uses only standard TS stuffs (await, Promise.all), hence is often easier to read for a developper that didn't write the code.