npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

postal.request-response

v0.3.1

Published

postal.js add-on that provides a request/response pattern API.

Downloads

301

Readme

postal.request-response

v0.3.1

What is it?

postal.request-response is an add-on for postal.js which gives postal a request/response pattern API alongside the normal messaging (publish/subscribe) API which postal's core supports. A publisher can invoke request instead of publish - this returns a promise which can be used to handle a "success" reply (via the success callback) or a timeout(if you've set one) and/or "error" reply via the error handler.

How do I use it?

This add-on adds a request method to the ChannelDefinition prototype, with a method signature of channel.request(options), where options can contain the following:

  • topic - the topic for the message
  • data - the message data
  • timeout - a timeout value in milliseconds. If the reply does not occur within this threshold, the promise moves to failed/rejected state, invoking the error handler the user passed to .then()
  • envelope - it's possible you may want to customize your envelope. If this is the case, you can pass a full envelope instead of topic and data, and it will be used to build up the message itself. *Important: you should not pass envelope and topic/data, choose one or the other, as the envelope option will be picked over topic/data if it exists.
  • replyTopic - the topic that should be used on the reply. This defaults to the requestId that is generated for this request.
  • replyChannel - the channel name that should be used on the reply. This defaults to postal.request-response, but you can override it to whatever you like.

The request method returns a promise, which you can call then on and pass in success & error handlers. The success handler receives the message data arg. The error handler takes a single err argument.

Enough Talk, Show Me Code

To make a request, you can do the following:

var chn1 = postal.channel("user");

chn1.request({
	topic: "last.login",
	data: { userId: 8675309 },
	timeout: 2000
}).then(
	function(data) {
		console.log("Last login for userId: " + data.userId + " occurred on " + data.time);
	},
	function(err) {
		console.log("Uh oh! Error: " + err);
	}
);

To handle requests:

// SUCCESS REPLY
var subscription = chn1.subscribe("last.login", function(data, envelope) {
	var result = getLoginInfo(data.userId);
    // `reply` uses a node-style callback, with error as the first arg
    // or data (for success) as the second
	envelope.reply(null, { time: result.time, userId: data.userId });
});

// ERROR REPLY
var subscription = chn1.subscribe("last.login", function(data, envelope) {
    var result = getLoginInfo(data.userId);
    // `reply` uses a node-style callback, with error as the first arg
    // or data (for success) as the second
    envelope.reply({ msg: "No such user" });
});

###Wait - What Promise Lib Are You Using?! That's up to you, actually. I have no desire to force another dependency on you. So, you get to pick. The only catch is you need to tell postal how to create a "deferred" instance and a "promise" instance.

By "deferred", I'm referring to the internal instance supported by most major libs that expose methods like resolve, reject, etc. In other words, the instance that has the ability to both observer and change state.

By "promise" instance, I'm referring to the value that you'd hand off to the caller which should have a then method.

Let's look at some examples:

####Using jQuery

// We need to tell postal how to get a deferred instance
postal.configuration.promise.createDeferred = function() {
	return new $.Deferred();
};
// We need to tell postal how to get a "public-facing"/safe promise instance
postal.configuration.promise.getPromise = function(dfd) {
	return dfd.promise();
};

####Using Q (v0.9)

// We need to tell postal how to get a deferred instance
postal.configuration.promise.createDeferred = function() {
	return Q.defer();
};
// We need to tell postal how to get a "public-facing"/safe promise instance
postal.configuration.promise.getPromise = function(dfd) {
	return dfd.promise;
};

####Using when.js

// We need to tell postal how to get a deferred instance
postal.configuration.promise.createDeferred = function() {
	return when.defer();
};
// We need to tell postal how to get a "public-facing"/safe promise instance
postal.configuration.promise.getPromise = function(dfd) {
	return dfd.promise;
};

####Using rsvp

// We need to tell postal how to get a deferred instance
postal.configuration.promise.createDeferred = function() {
	return RSVP.defer();
};
// We need to tell postal how to get a "public-facing"/safe promise instance
postal.configuration.promise.getPromise = function(dfd) {
	return dfd.promise;
};

##How Does It work? This is an add-on for postal.js - which is an in-memory message bus. The core behavior in postal is that publishers publish messages to which any number of susbcribers can listen (and subscribers never need a direct reference to the publisher(s)). These messages are "fire and forget". It's perfectly reasonable to set up your own "request/response" implementation using your own custom topics for both the request and response side of things, etc. In other words - you can achieve "request/response" behavior indirectly through "event/informational" messages, instead of "command/RPC" messages. However, there are times where having the clearly expressed intent of "request/response" is the best fit for the job - that's why I wrote this.

The request method wraps the publish call, and adds some extra fields to the envelope, so a request message may look something like this:

{
    "topic": "last.login",
    "data": {
        "userId": 8675309
    },
    "headers": {
        "replyable": true,
        "requestId": "d76b71be-d8d7-44ac-95d4-1d2f87251715",
        "replyTopic": "d76b71be-d8d7-44ac-95d4-1d2f87251715",
        "replyChannel": "postal.request-response"
    },
    "channel": "channel1",
    "timeStamp": "2014-04-23T04:03:56.814Z"
}

Notice the headers? This request has been given a unique ID (an RFC4122 version 4 compliant GUID). When postal sees this metadata on the envelope, it will add a reply method to the envelope before handing the envelope to the subscriber's callback method. This allows the subscriber a simple way to reply without having to worry about knowing the ID, reply topic and reply channel to use. By default, postal will use the requestId as the topic, and postal.request-reponse as the channel on the reply. An example reply to the above message might look like this:

{
    "channel": "postal.request-response",
    "topic": "d76b71be-d8d7-44ac-95d4-1d2f87251715",
    "headers": {
        "isReply": true,
        "isError": false,
        "requestId": "d76b71be-d8d7-44ac-95d4-1d2f87251715"
    },
    "data": {
        "time": "Wed, 23 Apr 2014 04:03:56 GMT",
        "userId": 8675309
    },
    "timeStamp": "2014-04-23T04:03:56.819Z"
}

WAIT A SECOND, JIM! I thought you said to never ever add behavior to the envelope?

Well - to be clear, I've said never publish behavior (functions). Aside from being an anti-pattern, publishing functions on your messages results in a payload that can't be serialized (to JSON), so the moment you need to send messages across boundaries (to node.js, an iframe, web worker, etc.), then you will have problems!

However, it is OK for postal to manage adding convenience behaviors to an envelope before it hands it to subscriber callbacks if that behavior can be generated dynamically based on envelope data that can be serialized. The above request message is easily serializable, and the remote instance of postal (in an iframe, for example) would handle adding the reply method to the envelope as if the message originated locally in that iframe.

##License? MIT. Go forth.