csf
Version:
Generator based flow control with context providing
211 lines (174 loc) • 6.73 kB
JavaScript
;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var isGenerator = require("is-generator");
var isFunction = require("is-function");
var isPromise = require("is-promise");
var isObject = require("is-object");
var isObjectLike = require("is-object-like");
var isError = require("is-error");
var _require = require("./constants"),
PAYLOAD = _require.PAYLOAD,
CANCEL = _require.CANCEL,
CANCELLED = _require.CANCELLED;
function isGeneratorResultLike(next) {
return next.hasOwnProperty("value") && next.hasOwnProperty("done") && typeof next.done === "boolean";
}
function extractPayload(value) {
if (isObjectLike(value) && value[PAYLOAD]) {
if (value[PAYLOAD] === "invoke") {
return value();
}
Reflect.deleteProperty(value, PAYLOAD);
return value;
}
return value;
}
module.exports = function run(next, args, previousNext) {
if (typeof args === "undefined") {
args = [];
}
// TODO: add object like
if (_typeof(this) !== "object" && typeof this !== "function" && typeof this !== "undefined") {
throw new Error("Context should be an object like or undefined. " + _typeof(this) + " given");
}
/* Reduce context */
var context = this || null;
/*
* Handles situation when result is a function as is, not a next part of the
* flow. Thus if you prefer to return function as final paylaod, you must to
* define [PAYLOAD] static property, to prevent execution in the flow
*/
if (next && next[PAYLOAD]) {
return Promise.resolve(next);
}
/*
* Is next unit is a functiom. it must be called with current props
* and store and result will go to the flow again
*/
if (isFunction(next)) {
try {
var result = Reflect.apply(next, context, args);
/* That function was just a final result factory */
/*
* if (next[RETURN_PAYLOAD]) {
* return Promise.resolve(result);
* }
*/
/* No arguments should be passed the text sequence */
return Reflect.apply(run, context, [result]);
} catch (e) {
return Promise.reject(e);
}
/* Handle case when next unit is an object */
} else if (isObject(next)) {
/* If next unit is a generator result */
if (isObject(previousNext) && isGeneratorResultLike(next)) {
/*
* Generator still in work, current value should be executed
* and after it next value should be getted
*/
if (!next.done) {
return Reflect.apply(run, context, [next.value, args]).then(function (rawValue) {
/* Extract payload */
var value = extractPayload(rawValue);
try {
/* In this case next is just a generator result, ??? */
if (previousNext.hasOwnProperty(CANCELLED)) {
return Promise.resolve(typeof previousNext[CANCELLED] === "undefined" ? previousNext[CANCELLED] : previousNext.return(value).value);
}
var nextValue = previousNext.next(value);
/**
* If generator has no closing `return` statement, it will
* return `undefined` values with done===true.
* Anyway we have to return last yielded value.
*
* I must to decide about necessary to return undefined
* with there is no return statement
*/
if (nextValue.done && typeof nextValue.value === "undefined") {
return Promise.resolve(rawValue);
}
return Reflect.apply(run, context, [nextValue,
/* No arguments should be passed to the next sequence */
[], previousNext]);
} catch (e) {
return Promise.reject(e);
}
})
/* If error hapends while execution generator next value */
.catch(function (error) {
try {
/* Handle case when generator work have been cancelled */
if (previousNext.hasOwnProperty(CANCELLED)) {
return Promise.resolve(typeof previousNext[CANCELLED] === "undefined" ? previousNext[CANCELLED]
/* TODO: What I must to return here? */
: previousNext.return(null).value);
}
/* Return error back to generator */
var nextValue = previousNext.throw(error);
/* Run next unit, provided by catch block */
return Reflect.apply(run, context, [nextValue,
/* No arguments should be passed to the next sequence */
[], previousNext]);
/*
* Handles error which may be throwed while handling
* sub-level error
*/
} catch (e) {
/* Hm. may be we shoudl handle it in a normal way? .... */
return Promise.reject(e);
}
});
/* If generator done */
}
return Reflect.apply(run, context, [next.value,
/* No arguments should be passed to the next sequence */
[]]);
} else if (isGenerator(next)) {
// Means it is generator
try {
var value = next.next();
var wrap = Reflect.apply(run, context, [value, args, next]);
wrap[CANCEL] = function (val) {
next[CANCELLED] = val;
};
return wrap;
} catch (e) {
return Promise.reject(e);
}
} else if (isPromise(next)) {
var cancel = void 0;
var _wrap = new Promise(function (resolve, reject) {
cancel = resolve;
next.then(function (next) {
Reflect.apply(run, context, [next,
/* No arguments should be passed to the next sequence */
[]]).then(resolve).catch(reject);
}).catch(function (err) {
Reflect.apply(run, context, [err instanceof Error ? err : new Error(err),
/* No arguments should be passed to the next sequence */
[]]).then(resolve).catch(reject);
});
});
_wrap[CANCEL] = function (val) {
return cancel(val);
};
return _wrap;
} else if (isError(next)) {
return Promise.reject(next);
/* Unexecutable payload */
} else if (next[PAYLOAD]) {
return Promise.resolve(next.value);
} else {
// Returns plain object
return Promise.resolve(next);
}
} else {
// Returns plain object
return Promise.resolve(next);
}
};
module.exports.PAYLOAD = PAYLOAD;
// module.exports.RETURN_PAYLOAD = RETURN_PAYLOAD;
module.exports.CANCELLED = CANCELLED;
module.exports.CANCELLED = CANCELLED;