UNPKG

ickyrr-gagarin

Version:

A current updated fork of gagarin

246 lines (197 loc) 7 kB
var Promise = require('es6-promise').Promise; var either = require('./index').either; var expect = require('chai').expect; var portscanner = require('portscanner'); module.exports = function genericPromiseChain(methods, myPrototype, options) { options = options || {}; var defaultAction = typeof options.action === 'function' ? options.action : canonical; var transform = options.transform; function GenericPromiseChain (operand, promise, context) { "use strict"; var self = this; this._operand = operand; this._promise = promise || (typeof operand === 'function' ? operand() : operand); this._context = context || {}; } GenericPromiseChain.prototype = Object.create(myPrototype); [ 'then', 'catch' ].forEach(function (name) { GenericPromiseChain.prototype[name] = function () { "use strict"; var context = this._context; var args = Array.prototype.map.call(arguments, function (func) { return func.bind(context); }); this._promise = this._promise[name].apply(this._promise, args); return this; }; }); GenericPromiseChain.prototype.always = function (callback) { "use strict"; return this.then(function (result) { callback(null, result) }, callback); }; GenericPromiseChain.prototype.sleep = function (timeout) { "use strict"; var self = this; return self.then(function () { return new Promise(function (resolve) { setTimeout(resolve, timeout); }); }); }; GenericPromiseChain.prototype.expectError = function (callback) { "use strict"; var pattern = ''; if (typeof callback === 'string') { pattern = callback; callback = function (err) { expect(err.message).to.contain(pattern) } } else if (callback instanceof RegExp) { pattern = callback; callback = function (err) { expect(err.message).to.match(pattern) } } else if (callback === undefined) { // noop callback = function () {}; } else if (typeof callback !== 'function') { throw new Error('argument for expectError must be a string, RegExp or a function'); } var self = this; return self.then(function () { throw new Error('error was not thrown'); }, function (err) { expect(err).to.be.instanceof(Error); return callback.call(this, err); }); }; GenericPromiseChain.prototype.noWait = function () { "use strict"; /** * Use the same operand and context, but don't wait for the current promise. */ return new GenericPromiseChain(this._operand, null, this._context); }; GenericPromiseChain.prototype.branch = function (context) { "use strict"; /** * Use the same operand and context (unless a new context is provided), and wait for the current promise. */ return new GenericPromiseChain(this._operand, this._promise, context || this._context); }; GenericPromiseChain.prototype.switchTo = function (generic) { if (!generic._operand || !generic.branch) { throw new Error('can only switchTo another generic'); } var current = this._promise; return generic.branch(this._context).then(function () { return current; }); }; GenericPromiseChain.prototype.yet = function (code, args) { "use strict"; var args = Array.prototype.slice.call(arguments, 0); var self = this; //-------------------------------- return self.catch(function (err) { return self.noWait().execute(code, args).then(function (errMessage) { throw new Error(err.message + ' ' + errMessage); }); }); }; GenericPromiseChain.prototype.getFreePort = function (action) { return this.__custom__(function (operand, done) { portscanner.findAPortNotInUse(3000, 9999, 'localhost', either(done).or(function (port) { done(null, port); })); }); }; GenericPromiseChain.prototype.methods = methods.concat([ '__custom__', 'catch', 'then', 'always', 'sleep', 'expectError', 'noWait', 'branch', 'yet', 'getFreePort', ]); GenericPromiseChain.prototype.__custom__ = function (action, onFail) { var self = this; // this function should either rethrow the error or call the retry function (passed as the second argument) onFail = onFail || function (err) { throw err }; self._promise = Promise.all([ typeof self._operand === 'function' ? self._operand() : self._operand, self._promise ]).then(function (all) { return new Promise(function (resolve, reject) { var operand = all[0]; if (!operand || typeof operand !== 'object') { reject(new Error('GenericPromiseChain: invalid operand')); } if (action.length === 3) { // looks like the user wants to receive the value of the previous promise ... action = partial(action, all[1]); } if (transform) { action = transform(action); } (function doAction(retryCount) { action.call(self._context, operand, either(function (err) { var retryUsed = false; function retry (repair) { retryUsed = true; // if nothing is provided, use a noop repair function repair = repair || function (operand, done) { setTimeout(done); } repair.call({}, operand, either(reject).or(function () { doAction(retryCount + 1); })); } retry.count = retryCount; try { onFail(err, retry); } catch (err) { return cleanError(reject)(err); // stop here } if (!retryUsed) { reject(new Error('The onFail callback should either throw an error or call the `retry` routine immediately.')); } }).or(resolve)); })(0); }); }); return self; }; methods.forEach(function (name) { "use strict"; /** * Update the current promise and return this to allow chaining. */ GenericPromiseChain.prototype[name] = function () { var args = Array.prototype.slice.call(arguments, 0); return this.__custom__(function (operand, done) { defaultAction.call(this, operand, name, args, done); }); }; }); function canonical (operand, name, args, done) { if (!operand[name]) { done(new Error('GenericPromiseChain: operand does not implement method: ' + name)); } else { args.push(done); operand[name].apply(operand, args); } } return GenericPromiseChain; } function cleanError(reject) { return function (err) { if (err && !(err instanceof Error)) { err = new Error(err.message || err.toString()); } reject(err); } } function partial(func, value) { return function () { var args = Array.prototype.slice.call(arguments, 0); args.unshift(value); return func.apply(this, args); } }