fcoin/lib/utils/co.js
Christopher Jeffrey 1e362c8166
refactor: minor.
2016-10-02 23:24:12 -07:00

328 lines
6.1 KiB
JavaScript

/*!
* co.js - promise and generator control flow for bcoin
* Originally based on yoursnetwork's "asink" module.
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var utils = require('./utils');
var every;
/**
* Execute an instantiated generator.
* @param {Generator} gen
* @returns {Promise}
*/
function exec(gen) {
return new Promise(function(resolve, reject) {
function step(value, rejection) {
var next;
try {
if (rejection)
next = gen.throw(value);
else
next = gen.next(value);
} catch (e) {
reject(e);
return;
}
if (next.done) {
resolve(next.value);
return;
}
if (!isPromise(next.value)) {
step(next.value, false);
return;
}
next.value.then(succeed, fail);
}
function succeed(value) {
step(value, false);
}
function fail(value) {
step(value, true);
}
step(undefined, false);
});
}
/**
* Execute generator function
* with a context and execute.
* @param {GeneratorFunction} generator
* @param {Object} ctx
* @returns {Promise}
*/
function spawn(generator, ctx) {
var gen = generator.call(ctx);
return exec(gen);
}
/**
* Wrap a generator function to be
* executed into a function that
* returns a promise.
* @param {GeneratorFunction}
* @returns {Function}
*/
function co(generator) {
return function() {
var gen = generator.apply(this, arguments);
return exec(gen);
};
}
/**
* Test whether an object is a promise.
* @param {Object} obj
* @returns {Boolean}
*/
function isPromise(obj) {
return obj && typeof obj.then === 'function';
}
/**
* Wrap a generator function to be
* executed into a function that
* accepts a node.js style callback.
* @param {GeneratorFunction}
* @returns {Function}
*/
function cob(generator) {
return function(_) {
var i, args, callback, gen;
if (arguments.length === 0
|| typeof arguments[arguments.length - 1] !== 'function') {
throw new Error((generator.name || 'Function') + ' requires a callback.');
}
args = new Array(arguments.length - 1);
callback = arguments[arguments.length - 1];
for (i = 0; i < args.length; i++)
args[i] = arguments[i];
gen = generator.apply(this, args);
return cb(exec(gen), callback);
};
}
/**
* Wrap a generator function to be
* executed into a function that
* accepts a node.js style callback.
* Only executes callback on error.
* @param {GeneratorFunction}
* @returns {Function}
*/
function con(generator) {
return function() {
var i, args, callback, gen;
if (arguments.length === 0
|| typeof arguments[arguments.length - 1] !== 'function') {
throw new Error((generator.name || 'Function') + ' requires a callback.');
}
args = new Array(arguments.length);
callback = arguments[arguments.length - 1];
for (i = 0; i < args.length; i++)
args[i] = arguments[i];
gen = generator.apply(this, args);
return exec(gen).catch(function(err) {
// Escape the promise's scope:
utils.nextTick(function() {
callback(err);
});
});
};
}
/**
* Wait for promise to resolve and
* execute a node.js style callback.
* @param {Promise} promise
* @returns {Promise}
*/
function cb(promise, callback) {
promise.then(function(value) {
utils.nextTick(function() {
callback(null, value);
});
}, function(err) {
utils.nextTick(function() {
callback(err);
});
});
}
/**
* Wait for a nextTick with a promise.
* @returns {Promise}
*/
function wait() {
return new Promise(function(resolve, reject) {
utils.nextTick(resolve);
});
};
/**
* Wait for a timeout with a promise.
* @param {Number} time
* @returns {Promise}
*/
function timeout(time) {
return new Promise(function(resolve, reject) {
setTimeout(resolve, time);
});
}
/**
* Wrap `resolve` and `reject` into
* a node.js style callback.
* @param {Function} resolve
* @param {Function} reject
* @returns {Function}
*/
function wrap(resolve, reject) {
return function(err, result) {
if (err) {
reject(err);
return;
}
resolve(result);
};
}
/**
* Call a function that accepts node.js
* style callbacks, wrap with a promise.
* @private
* @param {Object} ctx
* @param {Function} func
* @param {Array} args
* @returns {Promise}
*/
function _call(ctx, func, args) {
return new Promise(function(resolve, reject) {
args.push(wrap(resolve, reject));
func.apply(ctx, args);
});
}
/**
* Call a function that accepts node.js
* style callbacks, wrap with a promise.
* @param {Function} func
* @returns {Promise}
*/
function call(func) {
var args = new Array(Math.max(0, arguments.length - 1));
var i;
for (i = 1; i < arguments.length; i++)
args[i - 1] = arguments[i];
return _call(this, func, args);
}
/**
* Wrap a function that accepts node.js
* style callbacks into a function that
* returns a promise.
* @param {Function} func
* @param {Object?} ctx
* @returns {Function}
*/
function promisify(func, ctx) {
return function() {
var args = new Array(arguments.length);
var i;
for (i = 0; i < arguments.length; i++)
args[i] = arguments[i];
return _call(ctx || this, func, args);
};
}
/**
* Execute each promise and
* have them pass a truth test.
* @param {Promise[]} jobs
* @returns {Promise}
*/
every = co(function* every(jobs) {
var result = yield Promise.all(jobs);
var i;
for (i = 0; i < result.length; i++) {
if (!result[i])
return false;
}
return true;
});
/*
* This drives me nuts.
*/
if (typeof window !== 'undefined') {
window.onunhandledrejection = function(event) {
throw event.reason;
};
} else {
process.on('unhandledRejection', function(err, promise) {
throw err;
});
}
/*
* Expose
*/
exports = co;
exports.exec = exec;
exports.spawn = spawn;
exports.co = co;
exports.cob = cob;
exports.con = con;
exports.cb = cb;
exports.wait = wait;
exports.timeout = timeout;
exports.wrap = wrap;
exports.call = call;
exports.promisify = promisify;
exports.every = every;
module.exports = exports;