Skip to content

Instantly share code, notes, and snippets.

@mtth
Last active January 23, 2017 03:51
Show Gist options
  • Save mtth/e3e9b19d35fc86cae3a042ff641c0667 to your computer and use it in GitHub Desktop.
Save mtth/e3e9b19d35fc86cae3a042ff641c0667 to your computer and use it in GitHub Desktop.
Promisified Avro service API
/* jshint esversion: 6, node: true */
'use strict';
/** Sample code using the promisified service API. */
const avro = require('avsc');
const promisify = require('./promisify'); // See file below.
// A sample service with a single message. Note that this message's error type
// will be a _wrapped_ union.
const service = avro.Service.forProtocol(avro.readProtocol(`
protocol EchoService {
error SomeError { string details; }
error AnotherError { int code = -1; }
string upperCase(string str) throws SomeError, AnotherError;
}
`), {wrapUnions: true});
// Extract our custom errors' constructors to instantiate them idiomatically.
const SomeError = service.type('SomeError').recordConstructor;
const AnotherError = service.type('AnotherError').recordConstructor;
// A server for our service which accepts promise-based handlers.
const server = promisify.promisifyServer(service.createServer({silent: true}))
.onUpperCase(function (str) {
// OK case, we just return a value (if this computation was asynchronous,
// we would just return a promise instead).
return str.toUpperCase();
// All the following lines are valid exceptional flows!
//
// Throwing a custom error:
// throw new SomeError('bar');
// throw new AnotherError();
// throw {AnotherError: new AnotherError()}; // Wrapping also works.
//
// Throwing a "system error":
// throw new Error('foo');
// throw {string: 'baz'};
});
// Promise-based client.
const client = promisify.promisifyClient(service.createClient({server}));
client.upperCase('abc')
// OK case, the response is returned as expected.
.then(console.log)
// If a custom error is returned, it will be always unwrapped; this enables
// dispatching on the error's type:
.catch(SomeError, function (err) {
console.error(`got some error with details ${err.details}`);
});
/* jshint esversion: 6, node: true */
'use strict';
/** Helpers to provide a promise-based service API. */
const Promise = require('bluebird');
/**
* Transform (in-place) client RPC methods to return promises.
*
* Additionally, the new methods will unwrap any remote errors to enable the
* standard `.catch(MyError, fn)` bluebird idiom; see the attached example for
* details.
*/
function promisifyClient(client) {
const fn = Promise.promisify(client.emitMessage);
client.emitMessage = function (name, req, opts) {
return fn.call(this, name, req, opts)
.catch(function (err) {
if (typeof err.unwrapped == 'function') {
const cause = err.unwrapped();
if (cause.constructor.type.typeName === 'error') {
// Unwrap remote errors.
err = cause;
}
}
throw err;
});
};
return client;
}
/**
* Transform (in-place) a server to accept promise-based handlers.
*
* As a convenience, we also allow handlers to throw remote errors without
* having to wrap them first (even when the message's errors type is a wrapped
* union); see the attached example for details.
*/
function promisifyServer(server) {
const fn = server.onMessage;
server.onMessage = function (name, handler) {
const msg = server.service.message(name);
if (!msg || msg.oneWay) {
// Nothing to do in this case, just use the same handler.
return fn.call(this, name, handler);
}
const wrappedHandler = Promise.method(handler);
return fn.call(this, name, function (req, cb) {
wrappedHandler.call(this, req)
.then(function (res) { cb(undefined, res); })
.catch(function (err) {
if (
err && typeof err.wrapped == 'function' &&
msg.errorType.typeName === 'union:wrapped'
) {
// Allow handlers to throw custom error instances even when the
// message's error type is a wrapped union.
cb(err.wrapped());
} else {
cb(err);
}
});
});
};
return server;
}
module.exports = {promisifyClient, promisifyServer};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment