carlessistare / grpc-promise

GRPC promisify module for all Request/Response types: standard and stream
MIT License
82 stars 17 forks source link

Streaming-Response RPCs should not wait for end of stream #17

Open w4rum opened 3 years ago

w4rum commented 3 years ago

Currently, a promisified streaming-response function returns a promise that resolves when the server ends the stream. One use-case of streaming-response RPCs is, however, that the client processes the reponse stream while the server is still executing the response. With the current implemenation, this is not possible, as the client receives the data in one go, when the stream completes.

This also makes it impossible to use infinite streams, as that causes the client to wait indefinitely.

Minimal example, JS client:

const grpc = require('grpc')
const grpcPromise = require('grpc-promise')
const protoLoader = require('@grpc/proto-loader')
const packageDefinition = protoLoader.loadSync(...)
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition)
const client = new protoDescriptor.ExampleService('localhost:50051', grpc.credentials.createInsecure())
grpcPromise.promisifyAll(client)

client.exampleUnaryStream()
    .sendMessage({})
    .then(r => console.log(`RESPONSE ${JSON.stringify(r)}`))
    .catch(e => console.log(`ERROR ${JSON.stringify(e)}`))

Python Server (based on grpclib and betterproto, see this issue for details; this part is working and not the cause of the problem mentioned here):

class ExampleService(ServiceStub):
    service_name = "ExampleService"

    @rpc_unary_stream(name="ExampleUnaryStream")
    async def example_unary_stream(self, request: Empty) -> AsyncIterator[Empty]:
        for i in range(5):
            yield Empty()
            await asyncio.sleep(1)

server = Server([ExampleService()])
await server.start("localhost", 50051)
await server.wait_closed()

As a temporary work-around, I only promisify unary/unary functions:

const unaryFunctions = []
Object.keys(Object.getPrototypeOf(client)).forEach(functionName => {
    if (client[functionName].requestStream === false && client[functionName].responseStream === false) {
        unaryFunctions.push(functionName)
    }
})
grpcPromise.promisify(client, unaryFunctions)

I'm not quite sure how this could be done with promises but it would be nice to be able to register an onData callback somehow and still retain the nicer syntax that the promisified functions provide.

urko-b commented 1 year ago

I found an example on other repo which gave me knowledge to complete my task. I'm developing a go gRPC server wih a node client. I was expected to download a file from server using stream server pattern but with this package I found a lot of latency and unexpected problems while download stream data.

As you mentioned, data is supplied as whole when the stream server ends.

This guy really helped me: https://github.com/aditya-sridhar/grpc-streams-nodejs-demo/blob/master/client_grpc_server_stream.js