homer-helper
Version:
A test npm package
736 lines (628 loc) • 23.2 kB
JavaScript
/**
* 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) {
;
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