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

operations

v0.1.2

Published

A library for managing complex chains of asynchronous operations in Javascript.

Downloads

97

Readme

operations.js

Build Status

Sauce Test Status

Promises (e.g. q and libraries such as async.js are awesome but have limitations when it comes to defining complex dependencies between your chunks of code.

operations.js is a library that allows us to create well defined operations which are wrappers around javascript functions. We can then express dependencies between them, compose them, place them on queues and cancel them at any point during their execution.

It is inspired by the NSOperation and NSOperationQueue classes in Apple's Cocoa framework.

Main features:

  • Define asynchronous chunks of code as operations.
  • Define dependencies between these operations.
  • Cancellation of operations.
  • Composite operations.
  • Run these operations in queues.
  • Limit number of operations running on each queue.
  • Inter-queue dependencies between operations.

Contents

Usage

Single Operations

Operations can be created as follows:

var operation = new Operation(function (done) {
	setTimeout(function () {
		done(null, 'result');
	}, 1000);
});

// Fired on completion, errors or otherwise.
operation.completion = function () {
	console.log(this.error); // null
	console.log(this.result); // 'result'
};

operation.start();

We can also give our operations a name:

var operation = new Operation('Do some stuff...', function (done) {
	// Do some stuff...
});

This is useful for logging and debugging.

Composite

Operations

var op1 = new Operation(function (done) {
	// Do something.
	done('error');
});

var op2 = new Operation(function (done) {
	// Do something else.
	done(null, 'result of something else');
});

var compositeOperation = new Operation([op1, op2]);

console.log(compositeOperation.isComposite); // true

compositeOperation.completion = function () {
	console.log(this.result); // [undefined, 'result of something else'];
	console.log(this.error); // ['error', undefined];
	console.log(this.failed); // true
}

compositeOperation.start();

Functions

Composite operations can also be constructed using functions:

var op1 = function (done) {
    // Do something.
    console.log(this); // Reference to automatically generated Operation object.
    done('error');
};

var op2 = function (done) {
    // Do something else.
    console.log(this); // Reference to automatically generated Operation object.
    done(null, 'result of something else');
};

var compositeOperation = new Operation([op1, op2]);

Cancellation

Operations can be cancelled by calling cancel. You must handle this within the function that defines your operation, otherwise the operation will just run to completion.

var op1 = new Operation(function (done) {
	while(!this.cancelled) {
		// ... do some stuff.
		if (finished) done();
	}
});

op.cancel(function () {
	// Called on cancel if operation handled cancellation, otherwise called on completion.
});

Dependencies

Operations can depend on other operations before starting.

var op1 = new Operation(function (done) {
	// Do something.
	done('error');
});

var op2 = new Operation(function (done) {
	// Do something else.
	done(null, 'result of something else');
});

op2.addDependency(op1);

op1.start();
op2.start();

We can also indicate that a dependency must succeed:

op2.addDependency(op1, true);

op1.start();
op2.start();

In this case, op2 would fail if op1 failed.

Queues

We can construct queues of operations using an OperationQueue.

var queue = new OperationQueue(2); // Maximum 2 concurrent operations.
queue.addOperation(op1, op2, op3, op4);
queue.start();

We can have multiple queues, with dependencies between operations on different queues:

var queue = new OperationQueue('A queue', 2);
var anotherQueue = new OperationQueue('Another queue', 4);

op2.addDependency(op4);

queue.addOperation(op1, op2, op3);
anotherQueue(op4, op5);

queue.start();
anotherQueue.start();

Events

Operations

Changes in properties on the observation object can be observed:

operation.onCompletion(function () {
	// Fired on completion.
});

Queues

queue.onStart(function () {

});

queue.onStop(function () {

});

Subclassing

For really complex operations it's probably more appropriate to subclass Operation so that logic can be seperated out into multiple functions.

function MyOperation(completion) {
    if (!this) return new MyOperation(completion);
    Operation.call(this, 'My Awesome Operation', this._start, completion);
}

MyOperation.prototype = Object.create(Operation.prototype);

MyOperation.prototype._start = function (done) {
	this.doSomething();
	this.doSomethingElse();
	done();
};

MyOperation.prototype.doSomething = function () {
	// ...
};

MyOperation.prototype.doSomethingElse = function () {
	// ...
};

var op = new MyOperation(function () {
	// Completion.
});

op.start();

Logging

We can enable logging at multiple levels enabling us to monitor things such as num operations running, num operations running per queue and the success and failure of operations.

Operation

Operation.logLevel = Log.Levels.Info; // Global log level for operations.
op1.logLevel = Log.Levels.Info; // Override on a per operation basis.

Example logs:

INFO [Operation]: "My operation" has started. 
INFO [Operation]: "My other operation" has started. 
INFO [Operation]: "My operation" was cancelled.
INFO [Operation]: "My other operation" failed due to failure/cancellation of dependencies: ["My operation"]

Queue

OperationQueue.logLevel = Log.Levels.info; // Global log level for queues.
queue.logLevel = Log.Levels.Info; // Override on a per queue basis.

Example logs:

INFO [OperationQueue]: "My queue" now has 1 running operation.
INFO [OperationQueue]: "My queue" now has 2 running operations.
INFO [OperationQueue]: "My queue" now has 2 running operations and 1 queued operation.

Testing

When testing code using operations its usually a good idea to ensure that tests do not clobber each other:

afterEach(function () {
	var numOperationsRunning = Operation.numOperationsRunning;
	assert(!numOperationsRunning, 'There are still ' + numOperationsRunning.toString() + ' operations running);
});

Installation

Browser

Include using script tag as normal:

<script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script>
<script type="text/javascript" src="operations.min.js"></script>

Can also be installed via bower:

bower install operations --save

The classes are made available in the op property of window and hence are available globally as follows within the browser:

var Operation       = op.Operation,
    OperationQueue  = op.OperationQueue,
    Log             = op.Log;

NodeJS

Install via npm:

npm install operations --save

And then use as normal:

var operations          = require('operations'),
	Operation           = operations.Operation,
	OperationQueue      = operations.OperationQueue,
	Log                 = operations.Log;

Contributing

To get started first clone the repository:

git clone [email protected]:mtford90/operations.js.git

Then install the dependencies:

cd operations.js && npm install && npm install grunt-cli -g

Tests

Run

We can run the tests by using:

grunt test

This will also build tests/index.html. Opening this up in the browser will run the same tests in said browser.

Watch

We can watch for changes and automatically build and run the tests by using:

grunt watch

This is livereload enabled, so we can automatically run the tests in the browser and in the console for NodeJS at the same time.

Cross browser

Saucelabs is used for cross-browser testing. These tests are automated via a Travis build but can also be executed via a grunt task provided that you have a Saucelabs account.

First of all setup environment variables with your saucelabs username and API key. This can be placed in your .bashrc file:

export SAUCE_USERNAME=...
export SAUCE_ACCESS_KEY=...

Then run the grunt task:

grunt testSauce

And then head over to https://saucelabs.com/account where you'll see the tests executing.

AngularJS bindings

Coming soon to a repo near you: https://github.com/mtford90/operations.angular.js