GithubHelp home page GithubHelp logo

web5design / q-connection Goto Github PK

View Code? Open in Web Editor NEW

This project forked from kriskowal/q-connection

0.0 2.0 0.0 550 KB

A JavaScript library for communicating asynchronously with remote objects using promises.

License: MIT License

q-connection's Introduction

Build Status

Asynchronous Remote Objects

This library makes it possible for objects to communicate asynchronously between memory-isolated JavaScript contexts, including pipelining interactions with results. Promises serve as proxies for remote objects.

Q-Connection works in Node and other CommonJS module loaders like Browserify, Mr, and Montage.

This is how it looks:

var Q = require("q");
var Connection = require("q-connection");
var remote = Connection(port, local);

The remote object is a promise for the local object on the other side of the connection. Likewise, the other side of the connection will get a promise for your local object. You are not obliged to provide a local object, depending on which end of the connection is providing a service.

If the remote or local object is not serializable, like functions or objects with methods, the other side will receive a promise but you will have to “send messages” to the promise instead of interacting directly with the remote object. When you invoke a method on a remote object, you get a promise for the result and you can immediately pipeline a method call on the result. This is the secret sauce.

The port is any W3C message port, web worker, or web socket. In the W3C’s infinite wisdom, these do not have a unified API, but Q-Connection will normalize them internally.

// To communicate with objects in a worker
var worker = new Worker("worker.js");
var child = Connection(worker, local);
// Inside a worker, to communicate with the parent
var parent = Connection(this);
// To communicate with a remote object on the other side of
// a web socket
var socket = new WebSocket("ws://example.com");
var remote = Connection(socket, local);
// To communicate with a single frame on the same origin
// (multiple frames will require some handshaking event sources)
var iframe = document.frames[0];
var child = Connection(iframe.contentWindow, local, {
    origin: window.location.origin
})
// To communicate with a parent frame on the same origin
var child = Connection(window, local, {
    origin: window.location.origin
})
// With a message port
var port = new MessagePort();
var near = Connection(port[0]);
var far = Connection(port[1]);

Your local value can be any JavaScript value, but it is most handy for it to be an object that supports an API and cannot be serialized with JSON.

var Q = require("q");
var counter = 0;
var local = {
    "next": function () {
        return counter++;
    }
};

In this case, the local object has a "next" function that returns incremental values. Since the function closes on local state (the counter), it can't be sent to another process.

On the other side of the connection, we can asynchronously call the remote method and receive a promise for the result.

remote.invoke("next")
.then(function (id) {
    console.log("counter at", i);
});

The connection is bi-directional. Although you do not need to provide and use both local and remote values on both sides of a connection, they are available.

You can asynchronously interact with any value using the Q API. This chart shows the analogous operations for interacting with objects synchronously and asynchronously.

synchronous                asynchronous
------------------         -------------------------------
value.foo                  promise.get("foo")
value.foo = value          promise.put("foo", value)
delete value.foo           promise.del("foo")
value.foo(...args)         promise.post("foo", [args])
value.foo(...args)         promise.invoke("foo", ...args)
value(...args)             promise.fapply([args])
value(...args)             promise.fcall(...args)

All of the asynchronous functions return promises for the eventual result. For the asynchronous functions, the value may be any value including local values, local promises, and remote promises.

The benefit to using the asynchronous API when interacting with remote objects is that you can send chains of messages to the promises that the connection makes. That is, you can call the method of a promise that has not yet been resolved, so that message can be immediately sent over the wire to the remote object. This reduces the latency of interaction with remote objects by removing network round-trips.

A chain of dependent operations can be contracted from:

<-client     server->
a..
   ''--..
         ''--..
               ''--..
             ..--''
       ..--''
 ..--''
b..
   ''--..
         ''--..
               ''--..
             ..--''
       ..--''
 ..--''
c..
   ''--..
         ''--..
               ''--..
             ..--''
       ..--''
 ..--''

Down to:

<-client     server->
a..
b..''--..
c..''--..''--..
   ''--..''--..''--..
         ''--..--''..
       ..--''..--''..
 ..--''..--''..--''
 ..--''..--''
 ..--''

Where the dotted lines represent messages traveling through the network horizontally, and through time vertically.

Ports

Q-Connection handles a variety of message ports or channel types. They are all internally converted into a Q Channel. If you are using a message channel that provides a different API than this or a WebWorker, WebSocket, or MessagePort, you can adapt it to any of these interfaces and Q-Connection will handle it.

This is probably the simplest way to create a channel duck-type, assuming that you’ve got a connection instance of the Node variety.

var port = {
    postMessage: function (message) {
        connection.send(message);
    },
    onmessage: null // gets filled in by Q-Connection
};
connection.on("message", function (data) {
    port.onmessage({data: ""})
});
var remote = Connection(port, local);

Here's an example showing adapting socket.io to the message port.

var port = {
  postMessage: function (message) {
    socket.emit("message", message);
  },
  onmessage: null // gets filled in by Q-Connection
};
socket.on("message", function(data) {
  port.onmessage({data: data});
});
var remote = Connection(port, local);

Q Channels

  • get() returns a promise for the next message from the other side of the connection. get may be called any number of times independent of when messages are actually received and each call will get a promise for the next message in sequence.
  • put(message) sends a message to the remote side of the connection.
  • close(reason_opt) indicates that no further messages will be sent.
  • closed a promise that is fulfilled with the reason for closing.

Q-Connection exports an indefinite Queue that supports this API which greatly simplifies the implementation of adapters.

  • get() returns a promise for the next value in order that is put on the queue. get may be called any number of times, regardless of whether the corresponding value is put on the queue before or after the get call.
  • put(value) puts a message on the queue. Any number of messages can be put on the queue, indepent of whether and when the corresponding get is called.
  • close(reason_opt) indicates that no further messages will be put on the queue and that any promises for such messages must be rejected with the given reason.
  • closed a promise that is fulfilled when and if the queue has been closed.

Web Workers and Message Ports

Q-Connection detects ports by their postMessage function.

  • postMessage(message)
  • onmessage(handler(message))

Web Sockets

Q-Connection detects Web Sockets by their send function. It takes the liberty to start the socket and listens for when it opens.

  • send(message)
  • addEventListener(event, handler(event))
  • start()
  • open event
  • close event

Memory

Q-Connection uses an LRU cache of specified size. The default size is infinite, which is horribly leaky. Promises between peers will stick around indefinitely. This can be trimmed to something reasonable with the max option.

var remote = Connection(port, local, {max: 1024});

The least frequently used promises will be collected. If the remote attempts to communicate with a collected promise, the request will be rejected. This is fine if you have code in place to recover from rejections that will revive the working set of promises. The minimum working set will vary depending on the load on your service.

q-connection's People

Contributors

blai avatar coderunr avatar domenic avatar gozala avatar johnjbarton avatar kriskowal avatar pchoi avatar spearway avatar stuk avatar thibaultzanini avatar wpk- avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.