UNPKG

homer-helper

Version:
736 lines (628 loc) 23.2 kB
/** * Version: 1.0.0 * * Created by Homer * All rights reserved. * * Reference: jQuery. */ (function(global, factory) { "use strict"; const gTag = "HomerHelper"; if (typeof module === "object" && module.exports === "object") { console.info(`[${gTag}] module.exports exists.`); module.exports = global.document ? factory(global, true) : function(w, j) { console.info(`[${gTag}] global.document doesn't exist.`); if (!w.document) { const strLog = `[${gTag}] Requires a window with a document...!?`; console.info(strLog); throw new Error(strLog); } return factory(w, j); }; } else { console.info(`[${gTag}] No module.exports. To call factory(global) directly.`); factory(global); } })((typeof window !== "undefined" ? window : this), function(window, noGlobal) { "use strict"; const gTag = "HomerHelper"; if (!window) { console.error(`[${gTag}] Wrong window.`); } /* const jQuery = (window) ? window.jQuery : undefined; if (!jQuery) { // ToDo: ... remove this ... console.error(`[${gTag}] Wrong jQuery.`); } */ // --- HomerHelper --- const HomerHelper = function() { // To avoid typo like "func()" and "new func()". if (!(this instanceof HomerHelper)) { LogHelper.w(gTag, "[HomerHelper] Forgot \"new\"?"); return new HomerHelper(); } if (typeof this.init === "function") { if (this._members && this._members.isInitialized) { LogHelper.e(gTag, "[HomerHelper] Already initialized."); return {}; } this.init(); } }; // HomerHelper HomerHelper.fn = HomerHelper.prototype = {}; // ToDo: ... to remove this ... /* // --- jQuery.extend (need !?!?!?) --- // ToDo: ... remove this !? ... if (jQuery && jQuery.extend && typeof jQuery.extend === "function") { HomerHelper.extend = jQuery.extend; } else { //LogHelper.e(gTag, "jQuery.extend is not valid."); console.warn(`[${gTag}] jQuery.extend is not valid.`); HomerHelper.extend = function() {}; } */ // --- LogHelper --- const gTagLogHelper = "LogHelper"; const LogLevel = Object.freeze({ VERBOSE: 0, DEBUG: 1, INFO: 2, WARN: 3, ERROR: 4 }); const LogHelper = HomerHelper.LogHelper = Object.freeze({ LogLevel: LogLevel, state: { logLevel: LogLevel.VERBOSE, saveFunc: (() => {}) }, setLogLevel: function(logLevel = LogLevel.VERBOSE) { switch (logLevel) { case LogLevel.VERBOSE: case LogLevel.DEBUG: case LogLevel.INFO: case LogLevel.WARN: case LogLevel.ERROR: this.state.logLevel = logLevel; break; default: this.e(gTagLogHelper, "[setLogLevel] Wrong params."); break; } }, setSaveFunc: function(newSaveFunc = (() => {})) { this.state.saveFunc = newSaveFunc; }, v: function(tag, content) { const log = this.transLog("V", tag, content); console.debug(log); if (this.state.logLevel <= LogLevel.VERBOSE) { this.state.saveFunc(log); } }, d: function(tag, content) { const log = this.transLog("D", tag, content); console.log(log); if (this.state.logLevel <= LogLevel.DEBUG) { this.state.saveFunc(log); } }, i: function(tag, content) { const log = this.transLog("I", tag, content); console.info(log); if (this.state.logLevel <= LogLevel.INFO) { this.state.saveFunc(log); } }, w: function(tag, content) { const log = this.transLog("W", tag, content); console.warn(log); if (this.state.logLevel <= LogLevel.WARN) { this.state.saveFunc(log); } }, e: function(tag, content) { const log = this.transLog("E", tag, content); console.error(log); if (this.state.logLevel <= LogLevel.ERROR) { this.state.saveFunc(log); } }, /** * Reference: https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString */ getTimeString: function() { const date = new Date(); /* // ToDo: ... customized TimeZone !? ... const strMillisecond = (date.getMilliseconds() / 1000).toFixed(3).slice(2, 5); const strTime = date.toLocaleString("default", {hour12: false}) + "." + strMillisecond; */ const strTime = date.toISOString(); return strTime; }, transLog: function(type, tag, content) { const strTime = this.getTimeString(); const strLog = `${strTime}/${type} [${tag}] ${content}\n`; return strLog; } }); // LogHelper // --- Helpful functions. (reference to jQuery) --- const isPlainObject = HomerHelper.isPlainObject = function(obj) { if (!obj) { return false; } const proto = Object.getPrototypeOf(obj); // obj.__proto__ if (!proto) { // Objects with no prototype (e.g., `Object.create( null )`) are plain. return true; } return (proto === Object.prototype); }; const isEmptyObject = HomerHelper.isEmptyObject = function(obj) { for (const name in obj) { return false; } return true; }; const deepClone = HomerHelper.deepClone = function(obj) { let retObj; if (!obj) { return obj; } else if (typeof obj === "function") { return obj; } else if (Array.isArray(obj)) { retObj = []; } else if (typeof obj === "object") { retObj = {}; } else { return obj; } for (const name in obj) { const data = obj[name]; // // ToDo: ... prevent infinite loop or infinite recursive functions ... // if (data && (Array.isArray(data) || isPlainObject(data))) { retObj[name] = deepClone(data); } else { retObj[name] = data; } } return retObj; }; // --- MyBase --- const MyBase = HomerHelper.MyBase = function() { // To avoid typo like "func()" and "new func()". if (!(this instanceof MyBase)) { LogHelper.w(gTag, "[MyBase] Forgot \"new\"?"); return new MyBase(); } if (typeof this.init === "function") { if (this._members && this._members.isInitialized) { LogHelper.e(gTag, "[MyBase] Already initialized."); return {}; } this.init(); } }; MyBase.fn = MyBase.prototype = Object.freeze({ constructor: MyBase, getTag: function() { //return (this.constructor.name || "?"); return "MyBase"; }, /* need !? public/private privilege issue !? initPrivateScope: function() { return this; }, */ init: function() { // // ToDo: ... use this.getPrivate() instead of this._members ... (public/private scope issue) !?!?!? // if (!this._members) { this._members = {}; } this.isInitialized = this.isInitialized.bind(this); // ToDo: ... public/private scope issue !? ... this.throwINIError = this.throwINIError.bind(this); this.throwFNIError = this.throwFNIError.bind(this); // // ToDo: ... // this._members.isInitialized = true; return this; }, isInitialized: function() { if (this._members && this._members.isInitialized) { // ToDo: ... as above, use this.getPrivateMembers() instead ... !? return true; } return false; }, /* superPrototype: function() { //super: function() { if (this._members && this._members._superPrototype) { return this._members._superPrototype; } let currentPrototype = this.constructor && this.constructor.prototype; let pp = currentPrototype && currentPrototype.__proto__; let superPrototype = pp && pp.constructor && pp.constructor.prototype; if (superPrototype) { if (!(this._members)) { this._members = {}; } this._members._superPrototype = superPrototype; return superPrototype; } / * if (this instanceof MyBase) { return this.constructor.prototype.__proto__; } * / if (this.constructor && this.constructor.prototype) { return this.constructor.prototype.__proto__; } /////LogHelper.e(...); throw new Error("Wrong superPrototype()."); }, */ throwINIError: function() { const strTag = (this.getTag instanceof Function) ? this.getTag() : "?"; const strError = "Instance Not Initialized."; LogHelper.e(strTag, strError); throw new Error(`[${strTag}] ${strError}`); }, throwFNIError: function(funcName) { const strTag = (this.getTag instanceof Function) ? this.getTag() : "?"; if (funcName instanceof Function) { funcName = funcName.name; } const strError = "Function Not Implemented."; LogHelper.e(strTag, `[${funcName}] ${strError}`); throw new Error(`[${strTag}.${funcName}] ${strError}`); } }); // MyBase.fn // --- PromiseTask --- const PromiseTask = HomerHelper.PromiseTask = function() {}; // Do work and handle the flow by Promise. /* ToDo: ... !? if (!Promise) { LogHelper.e(gTag, "Promise is not supported."); } */ // ToDo: ... js has `enum` !?!?!? PromiseTask.State = { // begin Pendding: 0x00, // execute Execute: 0x10, Executing: 0x20, // result Result: 0x30, Timeout: 0x31, Cancelled: 0x32, Error: 0x40, // end Finally: 0x50, FinallyError: 0x60 }; PromiseTask.shared = {}; (function(s) { s.cancelAllDate = new Date(); s.lastPromise = Promise.resolve(); })(PromiseTask.shared); PromiseTask.fn = PromiseTask.prototype = Object.freeze({ __proto__: MyBase.prototype, constructor: PromiseTask, getTag: function() { return "PromiseTask"; }, _shared: PromiseTask.shared, init: function() { //LogHelper.v(this.getTag(), "[init] ."); //LogHelper.d(this.getTag(), `[init] m = ${JSON.stringify(this._members, null, 2)}.`); if (!this._members) { this._members = {}; } else if (this._members.state || this._members.createDate || this._members.executeDate || this._members.executingDate || this._members.isCancelled || this._members.isEventTriggered || this._members.waitTimeInMillis || this._members.intervalId || this._members.promiseResolve || this._members.isInitialized) { // Warn if members conflict. LogHelper.w(this.getTag(), `[init] this._members = ${JSON.stringify(this._members, null, 2)}.`); } // state this._members.state = PromiseTask.State.Pendding; // date this._members.createDate = new Date(); this._members.executeDate = null; this._members.executingDate = null; // cancel this._members.isCancelled = false; // wait / event this._members.isEventTriggered = false; // timeout this._members.waitTimeInMillis = -1; this._members.intervalId = null; // promise this._members.promiseResolve = null; // init this._members.isInitialized = true; /* this.xxxFunc = this.xxxFunc.bind(this); // ToDo: ... need ? */ return this; }, // --- state --- getState: function() { return this._members.state; }, // --- time info --- getCreateTimeInMillis: function() { return this._members.createDate.getTime(); }, getExecuteTimeInMillis: function() { if (this._members.executeDate) { return this._members.executeDate.getTime(); } return 0; }, getExecutingTimeInMillis: function() { if (this._members.executingDate) { return this._members.executingDate.getTime(); } return 0; }, // --- cancel --- cancelAll: function() { this._shared.cancelAllDate = new Date(); }, getCancelAllTimeInMillis: function() { if (this._shared.cancelAllDate) { return this._shared.cancelAllDate.getTime(); } return 0; }, cancel: function() { this._members.isCancelled = true; }, isCancelled: function() { if ((this.getCancelAllTimeInMillis() - this.getCreateTimeInMillis()) > 0) { return true; } if (this._members && this._members.isCancelled) { return true; } return false; }, // --- wait event / timeout --- getWaitTimeInMillis: function() { return this._members.waitTimeInMillis; }, isWaitingEvent: function() { return (0 < this._members.waitTimeInMillis); }, waitEvent: function(waitTimeInMillis = null) { if (this.isWaitingEvent()) { LogHelper.w(this.getTag(), "[waitEvent] Already waiting."); return; } if (typeof waitTimeInMillis === "number" && 0 < waitTimeInMillis) { this._members.waitTimeInMillis = waitTimeInMillis } else { LogHelper.w(this.getTag(), `[waitEvent] Wrong waitTime "${waitTimeInMillis}".`); } }, triggerEvent: function(...params) { if (this._members.isEventTriggered) { LogHelper.w(this.getTag(), "[triggerEvent] Already triggered."); return; } this._members.isEventTriggered = true; this._members.promiseResolve({ state: PromiseTask.State.Result, params: [...params] }); }, // --- execute --- // ToDo: ... this.isExecuted: function() { ...!? execute: function(...executeParams) { if (this._members.state >= PromiseTask.State.Execute) { LogHelper.w(this.getTag(), `[execute] Wrong state (${this._members.state}).`); return; } this._members.state = PromiseTask.State.Execute; this._members.executeDate = new Date(); LogHelper.v(this.getTag(), `[execute] ${this.getExecuteTimeInMillis()}.`); this.onExecute(...executeParams); // ***** ToDo: ... nested promise issues ????? ***** return this._shared.lastPromise = this._shared.lastPromise .then((function() { return this.executeNow(); }).bind(this)) .catch((function(error) { LogHelper.e(this.getTag(), `[execute] Unexpected error: ${error}.`); }).bind(this)); }, executeNow: /*this.executing =*/ function(...executeParams) { if (this._members.state >= PromiseTask.State.Executing) { LogHelper.w(this.getTag(), `[executing] Wrong state (${this._members.state}).`); return; } this._members.state = PromiseTask.State.Executing; this._members.executingDate = new Date(); LogHelper.d(this.getTag(), `[executing] ${this.getExecutingTimeInMillis()}.`); /* ToDo: ... doesn't need this !? if (promise && (promise instanceof Promise)) { LogHelper.v(this.getTag(), "[executing] To return passed Promise."); return promise; } */ return new Promise((function(resolve, reject) { try { this._members.promiseResolve = resolve; let result = this.onExecuting(...executeParams); if (result instanceof Promise) { //LogHelper.v(this.getTag(), "[executing] Return result (Promise)."); result = result.then(function(value) { return { state: PromiseTask.State.Result, params: [value] }; }); this._members.promiseResolve(result); } else if (!this.isWaitingEvent()) { //LogHelper.v(this.getTag(), "[executing] Resolve()."); this._members.promiseResolve({ state: PromiseTask.State.Result, params: [result] }); return; // Resolved directly, so it doesn't need timeout. } if (this.isWaitingEvent()) { LogHelper.d(this.getTag(), "[executing] Wait event..."); this._members.intervalId = setInterval((function() { try { if (this._members.state > PromiseTask.State.Executing) { if (this._members.intervalId) { clearInterval(this._members.intervalId); return; } LogHelper.w(this.getTag(), `[executing.interval] State = ${this._members.state}, Unhandled state.`); return; } if (this.isCancelled()) { LogHelper.d(this.getTag(), `[executing.interval] State = ${this._members.state}, Cancelled.`); this._members.promiseResolve({ state: PromiseTask.State.Cancelled }); return; } const d = new Date(); const diffTimeInMillis = d.getTime() - this.getExecutingTimeInMillis(); //LogHelper.v(this.getTag(), `[executing.interval] ${this._members.waitTimeInMillis}, ${d.getTime()} -> ${diffTimeInMillis}.`); if (this.getWaitTimeInMillis() < (diffTimeInMillis)) { LogHelper.d(this.getTag(), `[executing.interval] State = ${this._members.state}, Timeout (${this.getExecutingTimeInMillis()} -> ${diffTimeInMillis}).`); this._members.promiseResolve({ state: PromiseTask.State.Timeout }); } else { this.onTick(); } } catch (e) { reject(e); } }).bind(this), 500); } } catch (e) { LogHelper.e(this.getTag(), `[executing] ex: ${e}.`); reject(e); } }).bind(this)) .then((function(value) { LogHelper.v(this.getTag(), `[executing] .then(func(${JSON.stringify(value)})).`); if (PromiseTask.State.Result === value.state || PromiseTask.State.Executing === value.state) { this._members.state = PromiseTask.State.Result; this.onResult(...value.params); } else if (PromiseTask.State.Timeout === value.state) { this._members.state = PromiseTask.State.Timeout; this.onTimeout(); } else if (PromiseTask.State.Cancelled === value.state) { this._members.state = PromiseTask.State.Cancelled; this.onCancelled(); } else { const v = (value && value.state) ? value.state : value; LogHelper.e(this.getTag(), `[executing.resultHandler] Unexpected state (${v}).`); throw new Error(`Unexpected state (${v}).`); } }).bind(this)) .catch((function(error) { LogHelper.w(this.getTag(), `[executing] .catch(func(${error})).`); //console.log(`${error}`); //console.log(error); this._members.state = PromiseTask.State.Error; this.onError(error); }).bind(this)) .then((function() { //LogHelper.v(this.getTag(), "[executing] .finally."); if (this._members.intervalId) { clearInterval(this._members.intervalId); } this._members.state = PromiseTask.State.Finally; this.onFinally(); }).bind(this)) .catch((function(error) { try { LogHelper.e(this.getTag(), `[executing.FinallyError] ${error}.`); this._members.state = PromiseTask.State.FinallyError; } catch {} }).bind(this)); }, // --- handlers --- onExecute: function() { // Override it if needed. }, onExecuting: function() { this.throwFNIError("onExecuting"); }, onTick: function() { // Override it if needed. }, onResult: function() { this.throwFNIError("onResult"); }, onTimeout: function() { // Override it if needed. }, onCancelled: function() { // Override it if needed. }, // Reserved. This should not be called. onError: function(error) { LogHelper.e(this.getTag(), `[onError] ${error}.`); }, onFinally: function() { // Override it if needed. } }); // PromiseTask.fn // --- init setting (reference to jQuery) --- const init = HomerHelper.fn.init = function() { // ToDo: ... to remove this ... return this; }; //const init = HomerHelper.fn.init; init.prototype = HomerHelper.fn; ////var rootHomerHelper; // not used... // Register as a named AMD module. // ToDo: ... to check whether it is enough to support AMD and CommonJS ... !?!?!? if (typeof define === "function" && define.amd) { console.info(`[${gTag}] define.amd exists.`); define("HomerHelper", [], function() { return HomerHelper; }); } //HomerHelper.noConflict = function(deep) {...} // ToDo: ... need !? if (!noGlobal) { if (window.HomerHelper === HomerHelper) { console.info(`[${gTag}] window.HomerHelper has exists.`); } else { console.info(`[${gTag}] Set to window.HomerHelper.`); window.HomerHelper = HomerHelper; } } // --- END --- return HomerHelper; }); ////////////////////////////////////////////// // Homer/LogHelper ??? // console.log/debug/info/warn/error()... // :r!date => 2019年05月13日 下午 02:35:09