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

sinch

v0.4.0

Published

A powerful wrapper for interfaces which creates extendable, synchronisity-agnostic interfaces which can be seamlessly passed around to other desynchronous interfaces as if they were synchronous arguments.

Downloads

10

Readme

Sinch


Build Status


Sinch is a lightweight, powerful JavaScript module which allows developers to create APIs which act in a conistent manner regardless of whether they are synchronous (returning their values immediately) or asynchronous (forcing the user to utilize a callback).

Any arguments passed to a Sinch-wrapped function can be manipulated as normal, synchronous arguments. Using this callforward pattern, end users are able to pass methods from one Sinch API to another without having to understand that the operations are asynchronous at all.

When objects are passed to Sinch, they will be returned as prototype-based JavaScript classes. Even if an instantiated object takes some time to initialize, it may be utilized immediately in a procedural fashion, with all data accessible at the end of the chain using callbacks, or passable to other Sinch-based API endpoints. Once the object has been fully initialized, all queued commands will be run immediately, properly bound to the newly-created scope.

Sinch also provides an Extends: keyword to objects which allows for the recursive extension of one Sinch API by another.

Examples

Single Functions

Let's start with two simple asynchronous function which call other functions with their values.

function echo(message, callback) {
	callback("I'm echoing, "+message+".");
}

function log(message) {
	console.log(message);
}

If we were to call echo("hello world", console.log), we would get the response of, "I'm echoing, hello world." This is how traditional modules present their asynchronous methods.

The problem with that is that the end user must create an intermediary anonymous function each time it's necessary to pass a value from one endpoint to another. This code would end up logging to the console, "I'm echoing, hello world."

echo('hello world', function(message) {
	log(message);
});

This is a pattern that JavaScript developers are very used to! However, if we were to wrap both log and echo into Sinch, we would--without sacrificing our asynchronous nature--be able to present a much clear API to developers using our code, which is indistinguishable from procedural code.

This code would have the exact same effect as the previous code. In fact, the previous code can still be used when wrapping methods in Sinch.

log(echo(message));

Let's try something a little less abstract, and assume we're using a Sinch- based API which handles file transfers. We want to chain a lot of asynchrnous file operations together in order to piece together a file, and use dynamic arguments, with the last argument being considered a callback if it's a function. This also requires that we keep track of how many asynchronous operations are pending and nest callbacks.

The code for something like this can get ugly fairly quickly!

function cat() {
	var output, i, files = [], callback;
	function next() {
		if (!callback) return;
		for (var f in files) if (files[f]) output += files[f];
		callback(output);
	}
	for (i in arguments) {
		// Dynamic arguments; check the last argument for a callback.
		if (i==arguments.length-1 && typeof arguments[i]=="function") {
			callback = arguments[i];
		}
		pending++;
		async.file_exists(arguments[i], function(success) {
			pending--;
			if (pending==0) next();
			if (!success) return;
			pending++;
			async.file_read(arguments[i], function(data) {
				pending--;
				files[i] = data;
				if (pending==0) next();
			});
		});
	}
}

cat('readme.txt', 'file.txt', 'other.txt', console.log);

Assuming we didn't want to reinvent the file API, we could refactor the same code using Sinch to be much easier to understand, more pleasant to use, and far less error prone.

function cat() {
	var output = "";
	for (var i in arguments) output += arguments[i];
	return output;
}); cat = Sinch(cat);

function read(file, callback) {
	async.file_exists(file, function(exists) {
		if (!exists) callback('');
		else async.file_read(file, callback);
	});
}; read = Sinch(read);

cat(read('readme.txt'), read('file.txt'), read('other.txt'))(console.log);

We could even make the example do even more advanced things without sacrificing readability or adding much code.

function get(url) { $.ajax(url, callback); }
get = Sinch(get);

cat(read('readme.txt'), get('data.json'), read('other.txt'))(console.log);

And, since Sinch doesn't modify the original values of the objects passed into it, and follows the same standard callback pattern of other JavaScript code, we could simply our code even further.

get = Sinch($.ajax);

Objects

If we pass an object into Sinch, we'll get back a constructor function with methods attached to its prototype.

var Database = Sinch({
	
	dblib: require('dblib'),
	
	execute: function(op) {
		return this.dblib.execute(op);
	},
	
	find: function(filter, callback) {
		this.execute('find '+filter, callback);
	}
	
});

var db = new Database();
db.find('id=1', console.log);

We have two magic property names within this object, init and Extends.

Asynchronous Initialization

Providing an init method tells Sinch that this object will take some time to initialize, and that its interface should be presented through a dummy mechanism and then queued to be later executed when the object is ready.

For example:

var Database = Sinch({

	// [...]
	init: function(server, db, callback) {
		this.dblib.connect(server, db, function() {
			callback();
		});
	}
	
});

Note that we can use the exact same code from the previous example to utilize the Database API. Each method will be executed when all required objects have initialized.

var db = new Database();
db.find('id=1', console.log);

Extending

We could also use Extends to extend one API from another.

var LiteDB = Sinch({

	Extends: Database,
	
	dblib: require('dblite')
	
});

Interfaces

With the simple use of a [type, function] syntax within object definition, you can let Sinch know it should return a dummy interface of type, and run it once everything has been initialized.

var Cat = Sinch({

	init: function(name, callback) {
		console.log("Waking up a cat named", name);
		setTimeout(callback, 1000);
	},
	
	catchMouse: [Mouse, function() { 
		return new Mouse();
	}]
	
});

This allows the user of the interface to work with the returned object as if it were immediately available.

new Cat('Meow-Meow').catchMouse().squeak(console.log);

Notice that in catchMouse, we can type simply, return new Mouse(), despite the fact that the Mouse won't finish loading immediately, nor will the Cat!

All of the above methods of writing code are designed to work together. Try them out and see which patterns seem cleanest to you.

Roadmap

  • Error handling with traditional (e,data) callback structure.
  • Configurable timeouts which throw errors