Skip to content

Instantly share code, notes, and snippets.

@softwarespot
Last active April 23, 2017 14:07
Show Gist options
  • Save softwarespot/50b5e8cbf58d8999008a7128c64b8e43 to your computer and use it in GitHub Desktop.
Save softwarespot/50b5e8cbf58d8999008a7128c64b8e43 to your computer and use it in GitHub Desktop.
Chainer implementation
// Create an object for checking if an internal object error/value combination or a simple value
var _internal = {
name: 'Chainer'
};
function Chainer(initial) {
var internal = _isInternal(initial) ?
initial :
_createInternal(null, initial);
var fn = _isFunction(arguments[1]) ?
arguments[1] :
function () {
return internal.value;
};
// Any other value i.e. not a Promise-like object
if (!_isPromise(internal.value)) {
this.value = _tryFunction(fn, this, internal.error, internal.value);
return;
}
this.value = internal.value
.then(function (value) {
var internal = _isInternal(value) ?
value :
_createInternal(null, value);
// Invoke the next pipe function
return _tryFunction(fn, this, internal.error, internal.value);
})
.catch(function (err) {
// Pass an internal object through to the next "thenable", which will invoke the function
return _createInternal(err, undefined);
});
}
// Create a configuration object for "Chainer", whereby the Promise function can be
// specified
Chainer.config = {
Promise: Promise
};
Chainer.prototype = {
async: function (fn) {
var promise = new Chainer.config.Promise(function (success, error) {
try {
// Inverse the order to match that of when passing the "error" value
// and "success" value to the piped function
fn(error, success);
} catch (ex) {
error(ex);
}
});
return new Chainer(promise, function (err, value) {
// Check if no error ocurred inside the async pipe
return err === null ?
value :
err;
});
},
pipe: function (fn) {
return new Chainer(this.value, fn);
}
};
// Helper functions
// Create an internal object containing an error/value combination
function _createInternal(err, value) {
return {
error: err,
value: value,
_internal: _internal
};
}
// Wrapper to check if an argument is a function data type
function _isFunction(fn) {
return typeof fn === 'function';
}
// Wrapper for checking if an error-like argument is an internal error/value object
function _isInternal(obj) {
return obj && obj._internal === _internal;
}
// Wrapper for checking if an object is Promise-like i.e. contains a "thenable" function
function _isPromise(promise) {
return promise && typeof promise.then === 'function';
}
// Wrapper to invoke a callback function safely and return an internal error/value object
function _tryFunction(fn, context /* , args */) {
var error = null;
var value;
try {
// Remove the callback function and context arguments
value = fn.apply(context, Array.from(arguments).slice(2));
} catch (ex) {
error = ex;
}
return _createInternal(error, value);
}
// Chainer Example(s)
(new Chainer('A'))
.pipe(function (err, value) {
console.log(err, value);
return 'B';
})
.pipe(function (err, value) {
console.log(err, value);
return 'C';
})
.pipe(function (err, value) {
console.log(err, value);
// Can handle Promises, thus making all further pipes async
return new Promise(function (resolve) {
setTimeout(resolve.bind(null, 'D'), 200);
});
})
.pipe(function (err, value) {
console.log(err, value);
throw new Error('An unexpected error occurred with inside this pipe');
})
.pipe(function (err, value) {
if (err) {
console.log(err);
// Return "E" as the value, since the next pipe will handle this as a "default" value instead
return 'E';
}
console.log(value);
return 'F';
})
.pipe(function (err, value) {
console.log(err, value);
});
(new Chainer('Z'))
.async(function (error, success) {
setTimeout(success.bind(null, 'Y'), 200);
return 'ignore the following return statement value';
})
.pipe(function (err, value) {
console.log(err, value);
return 'X';
})
.pipe(function (err, value) {
console.log(err, value);
return 'W';
}).
pipe(function (err, value) {
console.log(err, value);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment