UNPKG

promise-task-flow

Version:

Enhanced Promise flow, which supported retry, delay, jump and so on.

198 lines (171 loc) 6.62 kB
/** * 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(); } }