import {assign} from '../utils/index' import {init as initlifeCycle, LIFECYCLE as LIFE} from './lifeCycle' import {IPlugin, IpluginOpts} from '../types/plugin' import {IObject, IState, IHandler, ILifeCycle, ILifecycleMap, IHookArgs} from '../types/baseType' enum STATE { READY = 0, PENDING = 1, FULFILLED = 2, REJECTED = 3 } export default class Usync { public root: IObject; public vessel: IObject = {}; public defferd: IHandler[]; public index: number; public state: number; public startTime: number; public endTime: number; public __name__: string; public __state__: number; public depth: number; public $parent: Usync; public fulfilledBroadcast() {} /** * Initialize a Usync APP * @param state * @param options */ constructor(state: IState, options?: IObject) { this.root = Array.isArray(state) ? state : typeof state === 'string' ? ((this.setName(state)) && {}) : typeof state === 'object' ? [state] : {} options = assign({}, options) if (options.name) { this.setName(options.name) } this.root.$name = this.__name__ this.defferd = [] this.index = -1 this.state = STATE.READY this.runHook(LIFE[LIFE.init], this) } get currentDefferd() { return this.defferd[this.index] } get prevDefferd() { return this.defferd[this.index - 1] } get nextDefferd() { return this.defferd[this.index + 1] } public setName(name: string) { this.__name__ = name return this } get name() { return this.__name__ } /** * Core method to add task * @param handler * @returns {Usync} */ public use(handler: IHandler | IHandler[]) { // Supoort Array syntax if (Array.isArray(handler)) { for (let childHandler of handler) { this.use(childHandler) } return this; } handler.$parent = this this.runHook(LIFE[LIFE.beforeUse], this, handler) this.defferd.push(handler) if (this.defferd.length === 1) { this.state = STATE.PENDING this.index = 0 } else { // Previous task has been FULFILLED, directly to the next task execution if (this.defferd[this.defferd.length - 2].__state__ === STATE.FULFILLED) { this.then() } } return this } /** * Run next task */ then() { // Add Support for time record this.currentDefferd.startTime = new Date().getTime() let argues = [this.root].concat(this.done.bind(this)) try { this.setRootProperty() this.runHook(LIFE[LIFE.taskStart], this.root) if (typeof this.currentDefferd === 'function') { let returnValue = this.currentDefferd.apply(this, argues) if (returnValue instanceof Promise) { returnValue.then(() => { this.done.call(this) }).catch(err => { throw err }) } } else if (this.currentDefferd instanceof Usync) { this.currentDefferd.fulfilledBroadcast = this.done.bind(this) this.currentDefferd.start() } } catch (err) { if (this.vessel.catch) { let errArgues = [err].concat([this.root], this.done.bind(this)) this.vessel.catch.apply(this, errArgues) } else { throw new Error(err) } } } /** * Update root state */ setRootProperty() { this.root.$current = this.currentDefferd this.root.$prev = this.prevDefferd this.root.$next = this.nextDefferd } /** * the next() */ done() { this.currentDefferd.__state__ = STATE.FULFILLED this.currentDefferd.endTime = new Date().getTime() this.setRootProperty() this.runHook(LIFE[LIFE.taskEnd], this.root) this.index++ // defferd running finished if (this.index === this.defferd.length) { this.state = STATE.FULFILLED this.defferd = [] this.index = -1 this.runHook(LIFE[LIFE.appEnd], this.root) // When a Usync instance set as a child task for another Usync instance // fulfilledBroadcast() will tell the parent it was fulfilled if (typeof this.fulfilledBroadcast === 'function') { this.fulfilledBroadcast() } return; } // Synchronous task call next, // subsequent task at this time has not yet been push, return directly if (this.currentDefferd === undefined) { return; } // Asynchronous task, subsequent task was already pushed // but didn't execute else if (this.currentDefferd.__state__ === undefined) { this.then() } } /** * Error catch * @param fn * @returns {Usync} */ public catch(fn: (err: any) => any) { this.vessel.catch = fn; return this; } /** * A Usync application does not run automatically, start() must be called */ public start() { this.runHook(LIFE[LIFE.appStart], this.root) this.startTime = new Date().getTime() this.then() } /** * Run life cycle hook * @param {String} name * @param args */ runHook(name: string, ...args: IHookArgs) { let hookQuene = `${name}Quene` this.lifecycleMap[hookQuene].forEach(hook => hook.apply(this, args)) if (this.protoLifecycleMap) { this.protoLifecycleMap[hookQuene].forEach(hook => hook.apply(this, args)) } } get protoLifecycleMap() { return Object.getPrototypeOf(this).lifecycleMap; } /** * Extend through life cycle * @param hooks */ public extend(hooks: ILifeCycle) { for (let key of Object.keys(this.lifecycleMap)) { let _key = key.replace('Quene', '') if (hooks[_key]) { this.lifecycleMap[key].push(hooks[_key]) } } } lifecycleMap = initlifeCycle(); static plugin(plugin: IPlugin, options?: IpluginOpts) { } static app(state: IState, options?: IObject) { return new Usync(state, options) } static extend(hooks: ILifeCycle) { } }