Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NodeJS] Promises and async/await #54

Open
alexkreidler opened this issue Sep 29, 2017 · 43 comments
Open

[NodeJS] Promises and async/await #54

alexkreidler opened this issue Sep 29, 2017 · 43 comments

Comments

@alexkreidler
Copy link

alexkreidler commented Sep 29, 2017

It would be nice if there was a grpc-tools and/or grpc_node_plugin option that would generate client and server code that follows the ES6 Promise and ES7 async/await model.

For example, instead of:
https://github.com/grpc/grpc/blob/98f8989a6712118549c432c9f91b6a516250014d/examples/node/static_codegen/greeter_client.js#L35-L37
We could write:

  [err, response] = await client.sayHello(request);

Or something similar. On the server side, instead of:
https://github.com/grpc/grpc/blob/98f8989a6712118549c432c9f91b6a516250014d/examples/node/static_codegen/greeter_server.js#L27-L31
We could write:

async function sayHello(call) {
  var reply = new messages.HelloReply();
  reply.setMessage('Hello ' + call.request.getName());
  return [null, reply]
}

Again, just ideas.

This feature would eliminate the need for wrapper code for users wanting to use this pattern. I would love to know if anyone is interested in this feature or planning on adding it. Thanks!

(moved from grpc/grpc#12751)

@murgatroid99
Copy link
Member

Thank you for moving this. I think this is an interesting idea that is worth exploring, but there are also some complications you haven't addressed.

On the client side, these functions already have return values, which would be shadowed by a naive implementation of this change, or would need to be merged with the promise object. In fact, it is already possible to get this basic functionality and shadow the return value by using util.promisify in the standard library. I think that merging the Promise objects with the existing return values would work fine, but there's a potential of creating some confusion when part of the object is promise based, and part is not. The main issue that impacts both unary and client streaming calls is that the return object is an event emitter that emits metadata events. So, to get both the response and the metadata from a call, using the async system you propose, the code would have to look something like this:

let call = client.sayHello(request);
call.on('metadata', metadata => {
  // Handle metadata
});
response = await call;  // An error would be thrown, not returned

This isn't so bad, because simply ignoring the metadata and awaiting the return value directly would still work. However, client streaming calls would have the additional issue that you have to write messages and end writing before you can get the response. that code would look more like this:

let call = client.clientStreamingCall();
call.write(message1);
call.write(message2);
call.end();
response = await call;

In this case, directly awaiting the return value would not work, and the function would actually never continue, because the client generally needs to call call.end() before they can expect to receive a response.

On the server side, promise-based unary handlers should be just fine, but for streaming handlers, they should expect to be calling call.on('data'...) and call.on('end',...) to handle the incoming message stream. Still, they can explicitly create and resolve the promise object, so that would not be too bad.

So, I think there's some work to be done here, but if you (or anyone else) submits a fleshed-out proposal for this API addition to https://github.com/grpc/proposal, I would be happy to add that change.

@bojand
Copy link

bojand commented Oct 4, 2017

Hello, Just wanted to add to the discussion. While I agree that this would be useful and nice, I think it should be approached carefully so we can get it "right" and in line with however Node core approaches async API's as that would probably be the more future-proof and idiomatic mechanism. I would still think for the time being that we have callback based API by default, at least while Node core is still callback-based by default. Expose async API to users explicitly if they ask for it. For example see this experimental proposal for promisified fs. And user's can promisify however they want / need. For example I have created a helper client library that promisifies calls if callbacks are not provided. On server side similarly I've created a minimalistic framework with API that is fully async based and very much inspired by Koa. PR's are welcome for improvements for both.

@marr
Copy link

marr commented Jan 18, 2018

@bojand I was excited to try out grpc-caller as it seemed to solve the async/await usage with grpc in node. However I came across bojand/grpc-inspect#10 which failed to load my protos containing nested packages. Hopefully there is some way to fix grpc-inspect to resolve the nested packages correctly.

@majelbstoat
Copy link

majelbstoat commented Jun 26, 2018

How about making them observables using something like RxJS @murgatroid99? I'm not an expert on them, but I believe they would support streaming and metadata a little more cleanly, and would be promise-compatible. (https://medium.com/@benlesh/rxjs-observable-interop-with-promises-and-async-await-bebb05306875)

https://github.com/kondi/rxjs-grpc has an implementation of this kind of thing.

(I currently do promisification on the client with bluebird, but because i'm using typescript, i can't use promisifyAll, and have to promisify each method individually 😕)

@aguynamedben
Copy link

aguynamedben commented Jul 16, 2018

I agree this is an important feature. I'm using https://github.com/carlessistare/grpc-promise#readme and it works as expected so far. I'm not a hardcore gRPC user, however, so I'm not sure what kind of "here be dragons" issues may pop up, but seems worth it for now!

@Jokero
Copy link

Jokero commented Jul 28, 2018

Promise is already must-have feature. For me callbacks are something from dawn of the dinosaurs :)

@xizhibei
Copy link

xizhibei commented Sep 9, 2018

Hey guys, I have just created the grpc-helper, a wrapper for the client side.

The promise API:

Unary call:

const message = await helper.SayHello({
  name: 'foo',
});

// withFullResponse: true
const { message, peer, status, metadata } = await helper.SayHello({
  name: 'foo',
});

Client stream call:

const stream = new stream.PassThrough({ objectMode: true });

const promise = helper.SayMultiHello(stream);

stream.write({name: 'foo1'});
stream.write({name: 'foo2'});
stream.write({name: 'foo3'});

stream.end();

// resolveFullResponse: true
const { message, peer, status, metadata } = await promise;
// { message: 'hello foo1,foo2,foo3' }

Besides, I add the features like dns service discovery, load balance and circuit break etc.

Currently in beta, you might as well give it a try, comments and PRs are welcome.

@zl810881283
Copy link

This issue open for a long time. Has any roadmap for async/await which is important today?

@murgatroid99
Copy link
Member

Three or four different approaches for using async/await with gRPC have been suggested in this issue so far. I am going to officially recommend that you use one of those.

In my earlier comment I pointed out some blocking issues with integrating promise support directly into the main API. None of those have changed in the intervening time, and I have not seen any proposals for resolving them.

@majelbstoat
Copy link

FWIW, I did suggest observables, which seem to be steaming compatible and promise compatible, and potentially worthy of exploration.

@murgatroid99
Copy link
Member

It's not really clear to me what an observable-based API would look like, so I can't tell how effective it would be at solving the problems I brought up.

In addition, the current API uses standard built in APIs like callbacks and streams, and using a non-standard library like observables has the downside that Node developers will be less familiar with the general APIs. Because of that downside, I would need a particularly good reason to switch to an observable-based API.

In the meantime, anyone who wants an observable-based API can use your rxjs-grpc library.

@ninja-
Copy link

ninja- commented Jun 22, 2019

it's still not implemented. what year is it?

@fatfatson
Copy link

my workaround is making a util script to modify the generated service.d.ts:

for any method like this:

  login(
    requestMessage: proto_user_pb.UserLoginRequest,
    metadata: grpc.Metadata,
    callback: (error: ServiceError|null, responseMessage: proto_user_pb.UserLoginResult|null) => void
  ): UnaryResponse;

can be converted to its async version:

async loginAsync(
    requestMessage: proto_user_pb.UserLoginRequest,
    metadata: grpc.Metadata,
): Promise<{
  err:ServiceError|null;
  value:proto_user_pb.UserLoginResult|null;
}>;

it's simple to use a regex replace:

require("replace")({
	paths: [p],
	regex: /(.+)\(\n(.+)\n(.+)\n.+responseMessage: (.+)\).+\n.+: UnaryResponse;/gm,
	replacement: (m, p1, p2, p3, p4) => {
		return (
			m +
			`
			async ${p1}Async(
				${p2}
				${p3}
				): Promise<{
					err:ServiceError|null;
					value:${p4};
				}>;
			`
		);
	}
});

with this runtime util:

export function promisifyGrpc(client) {
	//
	Object.keys(Object.getPrototypeOf(client)).forEach(function(functionName) {
		const originalFunction = client[functionName];
		// console.log(functionName, originalFunction);
		let newFunc = async (req, mt) => {
			return new Promise(fill => {
				//console.log('call me', functionName, req, mt);
				originalFunction.bind(client)(req, mt, (err, res) => {
					fill({ err, value: res });
				});
			});
		};
		client[functionName + 'Async'] = newFunc;
	});
}

@murgatroid99
Copy link
Member

As I stated in my first comment in this thread, if someone is willing to write up a proposal that solves the problems I brought up, preferably without breaking the API, we will implement that. Until then, the current code works just fine, and multiple solutions have been suggested in this thread for using alternative types of APIs.

@hassanajazlunchon
Copy link

Is there any update related to promises/async-await? I love using promises and hate callbacks(). I know there are many wrappers but is there any update in grpc library?

@spion-h4
Copy link

Here is a proposal:

  • Add .toPromise() to the call type. It automatically end()s the call and returns a promise for the result message, status and metadata. Any stream errors or gRPC errors will be propagated as rejections to the promise.
  • Add .writeAsync() that works exactly the same as .write but instead of taking a callback returns a promise. Any stream errors that caused the write to fail will be propagated there.

@murgatroid99
Copy link
Member

First, as mentioned in my first comment, the "proposal" I was asking for something more fleshed out that follows the format in the gRPC proposal repository.

In addition, you say "result message" singular, so I assume you're referring to unary and client streaming requests, which have exactly one response message. The pattern there seems a bit odd: you make a request and pass a callback, then call .toPromise() on the resulting object to get the results in a different way. Would the callback be ignored, or would it also be invoked? And I'm not really sure why end() would be called automatically like that. Promises would be used here for handling responses, so there is no reason that they should change when or how requests are sent.

Similarly, I don't really understand how .writeAsync() would fit in here. The write() function is part of the built in Node Stream API, and changing just that one method to return a promise would create inconsistencies. Plus, the write() function already doesn't require a callback.

@spion
Copy link

spion commented Feb 12, 2020

Regarding an official proposal, I understand but working on a proposal that goes in the wrong direction would definitely be unproductive, and that's highly likely given that I've only just now started thinking about this issue 😄

You would not pass a callback to make the request. There resulting UnaryCall, ServerStream, ClientStream or Duplex instances would be amended to have a .toPromise() method which would be able to collect the single message or array of messages into the result, together with any additional metadata.

Unary call example:

let call = await client.sayHello(request).toPromise()
// call.response, call.metadata call.status all available in the promise

For streaming calls, writeAsync() would be the equivalent of write that has been passed a callback. It will return a promise, which you are allowed to await, but you don't have to. This would allow smooth streaming that respects backpressure.

Client streaming example

let call = client.clientStreamingCall()
for (let message of messages) {
  await call.writeAsync(message);
}
let result = call.toPromise();

If toPromise() doesn't make it clear that this completes the stream automatically for streaming calls, the API can be made different there. Not sure what naming convention you would like, maybe something like .endAsync()

If you want only the response and don't care about status and metadata, you could have an additional convenience method e.g. .getResponseAsync(). I realize my names here are all over the place and need work, but I'm sure a decent and consistent naming convention is possible. With promises you can attach callbacks at a later time even after the promise is resolved, which means you can call .toPromise() and still use the other convenience methods after the fact to get an (already resolved) promise for the message, status or metadata.

readAsync() can be used as an alternative to data events, although given that streams now support async iteration it's probably not necessary. Even without it, you could do this:

let call = client.duplexCall()
for (let message of messages) {
  await call.writeAsync(message);
}
for await (let responseMessage of call) {
  // process response message here.
}

The write() function is part of the built-in Node Stream API, and changing just that one method to return a promise would create inconsistencies.

There is an issue with node streams, unfortunately. Node isn't going to add the methods necessary to improve promise ergonomics to node streams, as they would risk breaking a lot of libraries that add their own methods. grpc-node can't switch from node streams to something else because that would also break backward compatibility. As such, the only viable option is probably to extend the grpc-returned stream objects with more methods.

@murgatroid99
Copy link
Member

I'm still not following why you would want to connect getting the response as a promise to finishing writing to a stream. It seems to me that the user could just call end() and then toPromise().

From the current Stream documentation it looks like Streams already handle reading and writing asynchronously using async iterators, so I think it's not necessary to add additional explicit support for promise-based operations there.

@spion-h4
Copy link

spion-h4 commented Feb 15, 2020

a) Is it possible to continue writing once you request to get the entire response? If not, there is no reason not to end the writing when requesting the response. Is it possible to get the entire response without ending the writing, or will it result with a forever-pending promise? If its forever-pending, that's even more reason not to allow this invalid state.

b) No, async iterators only handle async reading. Async writing is not handled, it still requires plenty of choreography to do properly e.g. if (!writable.write(chunk)) { await once(writable, 'drain'); }. In addition, reading for single-message responses (like with unary calls) is just not considered with node streams.

Basically, node streams missed the chance to have a rich API. If they were like Dart streams and had a couple of convenience properties and methods, like say https://api.dart.dev/stable/2.7.1/dart-async/Stream-class.html#instance-properties there would've been no need to do anything. But they're missing, and that its highly unlikely node core will ever add them. See for example: nodejs/readable-stream#403

@murgatroid99
Copy link
Member

Writing the request messages and setting up a handler for response messages are independent actions. In the current unary and client streaming APIs, the callback to handle the response is passed when starting the call. That doesn't close the call for writing, it just sets up a handler to process when the response does eventually arrive. Any promise-based API should similarly create a Promise that will eventually handle the response when it arrives, but creating that Promise shouldn't actually change the state of the call.

As for async writing, the Streams documentation page has an entire section called Piping to Writable Streams from Async Iterators. I believe that the patterns there are sufficient for most use cases, and I think there is significant value to staying consistent with the built in streams API, so I don't think there is a good reason to add new async writing APIs just for grpc streams.

@spion
Copy link

spion commented Feb 19, 2020

Writing the request messages and setting up a handler for response messages are independent actions. In the current unary and client streaming APIs, the callback to handle the response is passed when starting the call. That doesn't close the call for writing, it just sets up a handler to process when the response does eventually arrive. Any promise-based API should similarly create a Promise that will eventually handle the response when it arrives, but creating that Promise shouldn't actually change the state of the call.

Promise APIs are typically designed differently. Its quite unusual to have the promise call be passive i.e. simply provide a promise without doing anything to create it. When such APIs are designed, they're typically exposed as a property, which you can use like so:

let result = await stream.completed;

A notable popular example for this are the DOM document readiness promises as described here: whatwg/html#1936 (API has been agreed on, implementation priority is low so it hasn't been implemented yet)

A popular general pattern when promisifying APIs is to take the callback-based API and add the async suffix. There is even an automated (albeit unfortunately not type-safe in TypeScript, its dependent types are not powerful enough to express it) way to do this included in one of the most popular libraries bluebird: http://bluebirdjs.com/docs/api/promise.promisifyall.html

If we take that pattern and apply it to streams, we get endAsync(), which ends the stream and returns a promise, as well as writeAsync(), which executes a write and returns a promise that will resolve at the same time the callback was resolved.

With the above, we have multiple options that conform to those principles:

  • endAsync(): Promise - ends the stream, doesn't return results
  • .ended: Promise - Doesn't end the stream, however it provides an underlying promise to wait for that event
  • .toPromise(): Promise<{metadata, result, status}> - ends the stream and gives back a promise for the results. I realize a different name that makes it more clear its an action would be better.
  • .metadata, .result, .status - they don't do any actions, but they resolve once the data becomes available,

The main reason I proposed the name toPromise() is RxJS. In RxJS, he toPromise() method subscribes to the observable effectively triggering any actions necessary to complete it. However I realize the name looks like it only does the conversion, and I agree a better one might be needed to indicate the stream was ended.

I believe that the patterns there are sufficient for most use cases, and I think there is significant value to staying consistent with the built-in streams API, so I don't think there is a good reason to add new async writing APIs just for grpc streams.

I guess my fundamental disagreement is that there is value in staying consistent with the streams API. It's pretty bad and is slowly being abandoned by modern userland libraries in recent years (e.g. see NestJS gRPC client implementation). It's not Node core's fault, it's just impossible to evolve its ergonomics without breaking userland libraries that extend the API, so it's not going to happen.

More proof that it's already too late for node streams; here is how to detect the end of a stream: https://github.com/mafintosh/end-of-stream/blob/master/index.js

Sticking to this API practically guarantees bad ergonomics. Which I guess doesn't make extending it a good idea, necessarily, but I'm sorry to say I don't have any better ideas to offer.

flolu added a commit to flolu/centsideas that referenced this issue May 3, 2020
@RizkiNfs
Copy link

RizkiNfs commented Jun 1, 2020

in this page there's example how to promisify grpc client, i think it's good alternative until grpc client support promise natively

@netpoetica
Copy link

netpoetica commented Mar 1, 2021

I understand the need for holding back until the best way is decided, but in terms of how people on the JS side work with things, at the very least I would think a simple util.promisify would would suffice. However, this leads to numerous issues.

Firstly, if you just do something like

import { promisify } from 'util';
const myClient = new MyClient(SERVER_ENDPOINT, credentials);
(async makeCall () {
  const awaitable = promisify(myClient.getAThing);
  const result = await awaitable({});
  console.log(result);
})();

the context of myClient gets lost, and you end up with some error in client.js callProperties

Exception has occurred: TypeError: Cannot read property '$channel' of undefined

ultimately requiring you to bind, like so:

import { promisify } from 'util';
const myClient = new MyClient(SERVER_ENDPOINT, credentials);
(async makeCall () {
  const awaitable = promisify(myClient.getAThing.bind(myClient));
  const result = await awaitable({});
  console.log(result);
})();

However, even after this, your results are some strange object:

{
  "wrappers_": null,
  "arrayIndexOffset_": -1,
  "array": [
    9,
    12,
    4096,
    8664,
    4095
  ],
  "pivot_": 1.7976931348623157e+308,
  "convertedPrimitiveFields_": {}
}'

I do not want to be the type to add to a list of complaints, but I am an experienced JS dev with a pretty good handle on callbacks, promises, and async/await, and getting this library to cooperate has been quite an uphill battle. Utilizing callbacks today is essentially a non-starter for most JS developers, due to the guarantee that it will ultimately land you in callback hell, so I am not too surprised about the complaints here, but I think if util.promisify actually was usable here, it would satisfy a large portion of JS devs needs.

Do you have any insight into this strange and unexpected behavior with util.promisify?

Quick Edit: that result is actually just the expected response obj, with all of the correct accessor methods. Remind me not to code too late at night :) anyhow, I feel your pain. gRPCs streaming and duplexes do put a wrench in standard usage of promises. Ideally all unary calls would be awaitable/thennable by default, with the other types of streaming requests as event emitters maybe. Either way, it looks like as long as you use util.promisify and bind to myClient, you will get what you expect - I am

@CMCDragonkai
Copy link

I found that this question https://stackoverflow.com/questions/39439653/events-vs-streams-vs-observables-vs-async-iterators/47214496 provides some answers as to how to convert the callback API into something more useful. I find the pull API makes more sense for both client and server streaming and unary behaviours of GRPC.

@aikoven
Copy link

aikoven commented Apr 5, 2021

I wrote a library on top of grpc-js. It uses Promises and Async Iterables for streaming.

https://github.com/deeplay-io/nice-grpc

@hgl
Copy link

hgl commented May 25, 2021

I think most, if not all, solutions for issues raised by @murgatroid99 in the first comment can be found in the design of the fetch API:

1

these functions already have return values, which would be shadowed by a naive implementation of this change, or would need to be merged with the promise object ...

For the cancellation feature, the client methods should accept a signal argument:

const c = new AbortController()
const res = await client.sayHello(msg, c.signal)

Maybe the extra arguments should be grouped, since there are many of them:

const c = new AbortController()
const res = await client.sayHello(msg, { signal: c.signal, metadata, options })

2

The main issue that impacts both unary and client streaming calls is that the return object is an event emitter that emits metadata events

IIUIC, metadata is an one-off event? Why not directly return it?

const { response, metadata, status, peer } = await client.sayHello(msg)

Very similar to the design came up by @xizhibei

Or if it really has be to async

const result = await client.sayHello(msg)
const metadata = await result.metadata() // or await result.response()

Very similar to fetch's two-step fetching.

3

However, client streaming calls would have the additional issue that you have to write messages and end writing before you can get the response

@xizhibei's stream design also solves this problem pretty elegantly:

const promise = helper.SayMultiHello(stream);
stream.write({
  name: 'foo1',
});
stream.write({
  name: 'foo2',
});
stream.write({
  name: 'foo3',
});
stream.end();
const result = await promise;

Taken directly from grpc-helper's readme.

4

I am going to officially recommend that you use one of those.

With all due respect, the problem with such recommendation is that it's very likely to end up recommending abandonware.

@arshabh-inspectlogs
Copy link

util.promisify works for all my needs. Thanks @murgatroid99

@ghost
Copy link

ghost commented Jul 23, 2021

For who are interested in how to promisify:

const parseArgs = require("minimist")
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import { promisify } from 'util'

const helloworldProtp = __dirname + "/protos/helloworld_service.proto"
import { ProtoGrpcType as helloworldProtoGrpcType } from './protos-type/helloworld_service'

const helloworldPackageDefinition = protoLoader.loadSync(helloworldProtp, {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
})

const helloworldObject = grpc.loadPackageDefinition(helloworldPackageDefinition) as unknown as helloworldProtoGrpcType;
const helloworld = helloworldObject.helloworld

function runClient() {
  const argv = parseArgs(process.argv.slice(2), {
    string: "target",
  })

  let target
  if (argv.target) {
    target = argv.target
  } else {
    target = "127.0.0.1:50059"
  }

  let user
  if (argv._.length > 0) {
    user = argv._[0]
  } else {
    user = "world"
  }

  const client = new helloworld.Greeter(target, grpc.credentials.createInsecure())
  const sayHello = promisify(client.sayHello).bind(client)
  const sayHelloAgain = promisify(client.sayHelloAgain).bind(client)

  const call_service = async (req, service_name) => {
    try {
      const res = await service_name(req)
      console.log("sayHello_Reply:", res.new_name, res.new_age, "typeof int64:", typeof res.new_age)
    } catch (error) {
      console.log("error", error);
    }
  }

  call_service({ name: "Alan", age: 20 }, sayHello)
  call_service({ name: "John", age: 30 }, sayHelloAgain)
}

runClient()

@forcemagic
Copy link

forcemagic commented Sep 15, 2021

Regarding servers with TypeScript, I think I wrote a hack-ish promisify for Unary calls, if this helps some people:

function promiseWrap<T, U>(call: (origCall: grpc.ServerUnaryCall<T, U>) => Promise<U>): grpc.handleUnaryCall<T, U> {
    return (oc: grpc.ServerUnaryCall<T, U>, cb: sendUnaryData<U>) => {
        call(oc).then((rval) => cb(null, rval)).catch(cb);
    }
}

Usage:

const methods: YourHandlers = {
    YourMethod: promiseWrap(async (call) => {
        // Your code here
    }),
};

I suppose similar could not be done for Bidi/Unidirectional streaming calls for somewhat obvious reasons (event-based-ness 😄)

@smnbbrv
Copy link

smnbbrv commented Jan 28, 2022

To my taste it is way better to create a typescript wrapper for the client directly.

const client = new SearchServiceClient('host', ChannelCredentials.createInsecure());
const searchServiceClient: Promisified<SearchServiceClient> = promisify(client);
const request = new SearchRequest().setQuery(query);
const response = await searchServiceClient.search(request);

This preserves all original signatures (with callback changed to Promise), so it still allows to use absolutely the same interceptors, metadata, etc, and that's why not invasive

See the promisify implementation with usage example

@LucasVeloso2017
Copy link

LucasVeloso2017 commented Jan 31, 2022

Hi guys, thats works very good for me.
im abstract what @fanda2013 does, and getting that result.

`
.....
import { promisify } from 'util'

const client = new mongoProto.MongoService(host grpc.credentials.createInsecure())
const add = promisify(client.Add).bind(tclient)
const result = await add(payload)
.....
`

@ErickJ3
Copy link

ErickJ3 commented Mar 2, 2022

This problem can be solved in a more sophisticated way with typescript, the code:

import { credentials, Metadata } from '@grpc/grpc-js';
import { promisify } from 'util';

import { SayHelloClient, sayHelloRequest, sayHelloResponse } from '../../models/sayHello';

class ClientServiceGrpc {
    private readonly client: SayHelloClient = new SayHelloClient('localhost:50051', credentials.createInsecure());

    public async sayHello(param: sayHelloRequest, metadata: Metadata = new Metadata()): Promise<sayHelloResponse> {
        return promisify<sayHelloRequest, Metadata, sayHelloResponse>(this.client.sayHello.bind(this.client))(param, metadata);
    }
}

export const clientServiceGrpc: ClientServiceGrpc = new ClientServiceGrpc();

Good luck 😋

@nahidakbar
Copy link

Still no official support for async/await/Promises? This is a serious issue. People have been trying to not write callbacks in node for the last 5 years. Makes applications hard to read and maintain. Adding noisy promisify boilerplate everywhere is also not ideal. That plain looks like you are using legacy deprecated tech.

@childrentime
Copy link

2022, please add promise.

@Loschcode
Copy link

October 2022, please add promise.

@nbabanov
Copy link

nbabanov commented Oct 14, 2022

@murgatroid99 What do you think about just having the Unary calls as promises and other to be as is? I am more than willing to submit an official proposal/pull request. To me there is no need to change the way the streaming calls work in any way.

I will later submit my way of doing this.

So who agrees with me on this?

@murgatroid99
Copy link
Member

I would prefer a solution here to cover both unary and client streaming requests, because those two currently handle results in the same way. But I will not reject a proposal that only changes unary. Please send your proposal so that we can all evaluate the details of your proposed change.

@novascreen
Copy link

@ErickJ3
you don't have to pass the types to promisify if you write it like this:

class ClientServiceGrpc {
    private readonly client: SayHelloClient = new SayHelloClient('localhost:50051', credentials.createInsecure());

    public async sayHello(param: sayHelloRequest, metadata: Metadata = new Metadata()): Promise<sayHelloResponse> {
        return promisify(this.client.sayHello.bind(this.client, param, metadata, {}))();
    }
}

by passing all possible sayHello arguments (except the callback of course) in bind we make it clear which call signature of sayHello we want and promisify no longer gets confused

@guissalustiano
Copy link

We chose to create a simple wrapper

export const grpc = new AppClient(url, creds());

type Call<Input, Ret> = (
  input: Input,
  callback: (err: ServiceError | null, ret?: Ret) => void,
) => ClientUnaryCall;

export const call = <Input, Ret>(call: Call<Input, Ret>, input: Input): Promise<Ret | undefined> =>  promisify(call).call(grpc, input)

And use:

const res = await call(grpc.sayHello, {name: "Gui"});

@zl810881283
Copy link

zl810881283 commented Jul 17, 2024 via email

@prenaissance
Copy link

I guess that the community did a better job with ts-proto and nice-grpc

@paskozdilar
Copy link

We chose to create a simple wrapper

export const grpc = new AppClient(url, creds());

type Call<Input, Ret> = (
  input: Input,
  callback: (err: ServiceError | null, ret?: Ret) => void,
) => ClientUnaryCall;

export const call = <Input, Ret>(call: Call<Input, Ret>, input: Input): Promise<Ret | undefined> =>  promisify(call).call(grpc, input)

And use:

const res = await call(grpc.sayHello, {name: "Gui"});

This inspired me to write a full wrapper for all four method types, based on promises and async generators:

https://gist.github.com/paskozdilar/196b212a1df66463487fa2b75dde049c

This wrapper also uses AbortSignal for cancelling method calls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests