usync
Version:
A lightweight library used for serial task control
261 lines (211 loc) • 6.78 kB
text/typescript
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)) && <IObject>{}) :
typeof state === 'object' ? [state] : <IObject>{}
options = assign({}, options)
if (<string>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 <IHandler>this.defferd[this.index]
}
get prevDefferd() {
return <IHandler>this.defferd[this.index - 1]
}
get nextDefferd() {
return <IHandler>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(<IHandler>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 <ILifecycleMap>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 = <ILifecycleMap>initlifeCycle();
static plugin(plugin: IPlugin, options?: IpluginOpts) {
}
static app(state: IState, options?: IObject) {
return new Usync(state, options)
}
static extend(hooks: ILifeCycle) {
}
}