promise-task-flow
Version:
Enhanced Promise flow, which supported retry, delay, jump and so on.
198 lines (171 loc) • 6.62 kB
JavaScript
/**
* author: daleoooo
*/
function defaultSetTimeoutFunc (callback, timeout) {
setTimeout(callback, timeout);
}
export default class PromiseFlow {
constructor (options = {}) {
const {
setTimeoutFunc = defaultSetTimeoutFunc, PromiseFunc = Promise,
retry = 0, delay = 0, beforeErrorReject
} = options;
this.options = Object.assign({}, options);
this.tasks = [];
this.ERROR_TYPE_NAME = 'PromiseFlowError';
this.setTimeoutFunc = setTimeoutFunc;
this.PromiseFunc = PromiseFunc;
this.retry = 0;
this.delay = 0;
this.beforeErrorReject = beforeErrorReject;
this.options = options;
return this;
}
_isFunction (func) {
return typeof func === 'function';
}
_throwError (message) {
const err = new Error(message);
err.type = this.ERROR_TYPE_NAME;
throw err;
}
_throwPromiseError (message) {
const err = new Error(message);
err.type = this.ERROR_TYPE_NAME;
return this.PromiseFunc.reject(err);
}
_getDelayPromise (delay) {
if (delay <= 0) {
return this.PromiseFunc.resolve();
}
return new this.PromiseFunc(resolve => {
this.setTimeoutFunc(resolve, delay);
});
}
_runSingleTaskWithoutRetry ({ task, lastError, lastResult, retriedError, retriedTimes }) {
const promise = task(lastError, lastResult, { retriedError, retriedTimes });
if (!this._isFunction(promise.then)) {
return this._throwPromiseError('task must return a promise');
}
return promise;
}
_runSingleTask (task, options, lastError, lastResult) {
const { retry = 0, delay = 0, onlyRetryDelay = false, beforeRetry } = options;
const context = this;
let retriedTimes = 0;
function retryRunSingleTask (err, scopedDelay = 0) {
// use scopedDelay first
const d = scopedDelay || delay;
if (err && err.type === context.ERROR_TYPE_NAME) {
return context.PromiseFunc.reject(err);
}
const retriedError = err;
const getTaskPromiseWithDelay = () => {
if ((!onlyRetryDelay || retriedTimes) && d) {
return context._getDelayPromise(d).then(() => context._runSingleTaskWithoutRetry({
task, lastError, lastResult, retriedError, retriedTimes
}));
}
return context._runSingleTaskWithoutRetry({
task, lastError, lastResult, retriedError, retriedTimes
});
}
const taskPromise = getTaskPromiseWithDelay();
retriedTimes += 1;
if (retriedTimes > retry) {
return taskPromise;
}
return taskPromise.catch(e => {
if (context._isFunction(beforeRetry)) {
const ret = beforeRetry(e, { retriedTimes });
if (context._isFunction(ret.then)) {
return ret.then(res => {
if (res && res.retry === false) {
if (res.err) {
return context.Promise.reject(res.err);
}
return res.data;
}
return retryRunSingleTask(e, res.delay);
});
}
if (ret && ret.retry === false) {
if (ret.err) {
return context.Promise.reject(ret.err);
}
return ret.data;
}
return retryRunSingleTask(e, ret.delay);
}
return retryRunSingleTask(e);
});
}
return retryRunSingleTask();
}
add (task, options = {}) {
if (!this._isFunction(task)) {
return this._throwError('task must be a function');
}
this.tasks.push({ task, options });
return this;
}
findTaskInexByName (name) {
for (let i = 0; i < this.tasks.length; i ++) {
if (name === this.tasks[i].options.name) { return i; }
}
}
run () {
const context = this;
let taskIndex = 0;
function runSingleTask (lastError, lastResult) {
const { task, options } = context.tasks[taskIndex];
const { ignoreError = false, initialValue } = options;
return context._runSingleTask(task, options, lastError, lastResult || initialValue).catch(err => {
if (err && err.type === context.ERROR_TYPE_NAME) {
throw err;
}
const runNext = () => {
taskIndex += 1;
if (taskIndex >= context.tasks.length) {
return err;
}
return runSingleTask(err);
};
// if ignore error then run next task
if (ignoreError) {
return runNext()
}
const fork = (res = {}) => {
if (typeof res !== 'object') {
return context.PromiseFunc.reject(err);
}
if (res.retryOnce) {
return runSingleTask(err, res);
} else if (res.runNext) {
return runNext();
}
return context.PromiseFunc.reject(err);
}
if (context.beforeErrorReject) {
const ret = context.beforeErrorReject(err, options);
if (ret && context._isFunction(ret.then)) {
return ret.then(res => {
return fork(res);
}).catch(e => {
return context.PromiseFunc.reject(e);
});
}
return fork(ret);
}
return context.PromiseFunc.reject(err);
}).then(res => {
taskIndex += 1;
if (taskIndex >= context.tasks.length) {
return res;
}
return runSingleTask(null, res);
});
}
return runSingleTask();
}
}