UNPKG

asclasit

Version:

ASync CLasses + ASync ITerators

514 lines (410 loc) 12.3 kB
const $ = require('../func'); require('./acc'); const Iter = require('../iter'); const util = require('util'); const streams = require('../compat/stream'); const streamFinished = streams.finished; const {func_} = $; func_(function _bind(wrapper, binder, object, ...allArgs) { const [method, ...args] = allArgs; if (typeof method === 'function') return binder(wrapper, method, object || this, ...args); if (typeof method === 'string') { const obj = object || this; return binder(wrapper, obj[method], obj, ...args); } if (method && method[Symbol.iterator]) { const obj = object || this; const wrap = $(); const arg0 = args[0]; if (arg0 == null) { for (const key of method) wrap[key] = binder(wrapper, obj[key], null); } else if (typeof arg0 === 'boolean') { const rest = args.slice(1); if (arg0) for (const key of method) wrap[key] = binder(wrapper, obj[key], obj, ...rest); else for (const key of method) wrap[key] = binder(wrapper, obj[key], wrap, ...rest); } else { for (const key of method) wrap[key] = binder(wrapper, obj[key], ...args); } return wrap; } if (typeof object === 'function') return binder(wrapper, object, null); if (typeof object === 'object') throw new $.NotImplementedError(); return binder(wrapper, this[object], ...allArgs); }); func_(function bind_(wrapper, ofn, obj, ...args) { let func = wrapper(ofn); if (obj !== null) func = func.bind(obj, ...args); return func; }); const boundOnce = new WeakMap(); func_(function bindOnce__(to) { return function (wrapper, ofn, obj, ...args) { const byRef = to && (typeof to === 'object' || typeof to === 'function'); let funcBinds = boundOnce.get(ofn); let func, map; if (!funcBinds) { funcBinds = Object.create(null); funcBinds.val = new Map(); funcBinds.ref = new WeakMap(); boundOnce.set(ofn, funcBinds); func = false; } map = byRef ? funcBinds.ref : funcBinds.val; if (func == null) { func = map.get(to); } if (!func) { func = wrapper(ofn); if (obj !== null) func = func.bind(obj, ...args); map.set(to, func); } return func; }; }); func_(function promisify(object, ...args) { return $._bind.call(this, util.promisify, $.bind_, object, ...args); }); func_(function promisifyOnce(to, object, ...args) { return $._bind.call(this, util.promisify, $.bindOnce__(to), object, ...args); }); func_(function bind(object, ...args) { return $._bind.call(this, $.echo, $.bind_, object, ...args); }); func_(function bindOnce(to, object, ...args) { return $._bind.call(this, $.echo, $.bindOnce__(to), object, ...args); }); func_(function bound_(func) { return function _bound(...args) { return func.bind(this, ...args); } }); //TODO: bindJob in ClAs func_(function finished(stream, opts) { return streamFinished(stream, opts); }); func_(function finishedRead(stream, opts) { return streamFinished(stream, {writable: false, ...opts}); }); func_(function finishedWrite(stream, opts) { return streamFinished(stream, {readable: false, ...opts}); }); func_(function all(promises) { return Promise.all(promises); }); func_(function race(promises) { for (const idx in promises) { const promise = promises[idx]; if (!(promise instanceof Promise)) return [idx, promise]; if ($.promiseIsError in promise) { const data = promise[$.promiseValue]; if (promise[$.promiseIsError]) return [idx, new $.RaceError(data)]; return [idx, data]; } } return racePending(promises); }); const raceMap = new Map(); $.promiseValue = Symbol('$.promiseValue'); $.promiseIsError = Symbol('$.promiseIsError'); function racePending(promises) { let trigger; const result = new Promise((resolve, reject) => { trigger = {resolve, reject}; }); const tMap = new Map(); trigger.map = tMap; for (const idx in promises) { const promise = promises[idx]; let map = raceMap.get(promise); if (!map) { map = new Map(); raceMap.set(promise, map); promise.then(raceDone(promise, false), raceDone(promise, true)); } map.set(trigger, idx); tMap.set(map, idx); } return result; } function raceDone(promise, error) { return (data) => { const map = raceMap.get(promise); //if (!map) return; raceMap.delete(promise); promise[$.promiseIsError] = error; promise[$.promiseValue] = data; for (const [trigger, idx] of map.entries()) { for (const submap of trigger.map.keys()) { submap.delete(trigger); } trigger.map.clear(); delete trigger.map; const action = trigger.resolve; delete trigger.resolve; delete trigger.reject; action([idx, error ? new $.RaceError(data) : data]); } map.clear(); }; } func_(function reject(value) { const promise = Promise.reject(value); promise.catch($.echo); return promise; }); $.RaceError = class RaceError extends Error { constructor(error) { super('Error in $.race'); this.error = error; } } const eventMethods = { once: ['once'], on: ['addListener', 'addEventListener', 'on'], off: ['removeListener', 'removeEventListener', 'off'], }; func_(function grabEvents(from, events, { methods = eventMethods, limit = Infinity, grabbed = [], handler, } = {}) { if (!(limit > 0)) return grabbed; let on, off; for (const method of methods.on) if (typeof from[method] === 'function') { on = method; break; } if (!on) return null; for (const method of methods.off) if (typeof from[method] === 'function') { off = method; break; } if (!off) return null; let subs = Object.create(null); if (typeof events === 'string') events = events.split(','); if (!(events instanceof Set)) events = new Set(events); let report; const wait = new Promise(resolve => report = resolve); const stop = () => { if (!subs) return grabbed; for (const event of events) { const func = subs[event]; delete subs[event]; from[off](event, func); } subs = null; report(grabbed); return grabbed; }; wait.stop = stop; let n = 0; const grab = function (event, ...args) { if (!subs || n >= limit) return; grabbed.push({event, args, at: new Date()}); if (++n >= limit) stop(); if (handler) return handler.call(this, event, ...args); }; for (const event of events) { const func = grab.bind(this, event); from[on](event, func); subs[event] = func; } return wait; }); func_(async function firstEvent(from, resolves, rejects, opts = {}) { if (!rejects) rejects = ['error']; if (typeof resolves === 'string') resolves = resolves.split(','); if (typeof rejects === 'string') rejects = rejects.split(','); if (!(rejects instanceof Set)) rejects = new Set(rejects); const promise = $.grabEvents(from, new Set([...resolves, ...rejects]), {...opts, limit: 1}); opts.stop = promise.stop; const [grabbed] = await promise; if (!grabbed) return null; Object.assign(opts, grabbed); const {event, args} = grabbed; if (rejects.has(event)) throw args[0]; return args[0]; }); const callOnceMap = new WeakMap(); async function callOnceExpire(fn, msec) { await $.delayMsec(msec); callOnceMap.delete(fn); } async function callOnceTimer(obj, fn, msec, ...args) { obj.promise = fn.call(this, ...args); await obj.promise.catch($.null); if (msec) { obj.timer = callOnceExpire(fn, msec); } else { callOnceMap.delete(fn); } } function callOnceSet(fn, msec, ...args) { const obj = {}; obj.timer = callOnceTimer.call(this, obj, fn, msec, ...args); callOnceMap.set(fn, obj); return obj.promise; } func_(async function once(fn, msec, ...args) { if (typeof fn !== 'function') fn = this[fn]; const has = callOnceMap.get(fn); if (has) return await has.promise; const promise = callOnceSet.call(this, fn, msec, ...args); const res = await promise; return res; }); func_(async function only(fn, msec, ...args) { if (typeof fn !== 'function') fn = this[fn]; while (true) { const has = callOnceMap.get(fn); if (!has) break; await has.timer; } const promise = callOnceSet.call(this, fn, msec, ...args); const res = await promise; return res; }); const throttleMap = new WeakMap(); func_(function accCall(fn, msec, obj) { if (typeof fn !== 'function') fn = this[fn]; const cur = throttleMap.get(fn); let acc; if (cur) { acc = cur.acc; $.accumulate(acc, obj); if (msec) cur.time = msec; return cur.finaled; } else { acc = $.initAcc(obj); $.accumulate(acc, obj); } const newCur = {acc, time: msec, ctx: this}; throttleMap.set(fn, newCur); return timeThrottleTimeout(fn); }); async function timeThrottleTimeout(fn) { const cur = throttleMap.get(fn); const {acc, time, ctx, ok, nok} = cur; let has = false; const iter = acc[Symbol.iterator]; if (iter) { for (const key of iter.call(acc)) { has = true; break; } } else { for (const key in acc) { has = true; break; } } if (!has) { throttleMap.delete(fn); if (cur.ok) cur.ok(null); return null; } cur.acc = $.initAcc(acc); try { cur.finaled = new Promise((ok, nok) => { cur.ok = ok; cur.nok = nok; }); cur.finaled.catch($.null); delete cur.result; delete cur.error; const res = await fn.call(ctx, acc); cur.result = res; if (ok) ok(res); else return res; } catch (err) { cur.error = err; if (nok) nok(err); else throw err; } finally { if (time) { setTimeout(timeThrottleTimeout, time, fn); } else { setImmediate(timeThrottleTimeout, fn); } } } func_(function accCallCached(fn) { if (typeof fn !== 'function') fn = this[fn]; const pending = throttleMap.get(fn); if (!pending) return null; return pending.acc; }); func_(function throw_(title, {exitCode} = {}) { return function _throw(err) { let out = !err ? err : err.stack || err.message || err.type || err.code || err; if (title) out = `${title}\n${out}`; console.error(out); if (exitCode) process.exit(exitCode); } }); const locks = new Map(); func_(async function lock(to) { while (true) { const ex = locks.get(to); if (!ex) break; await ex.promise; } let unlock; const promise = new Promise(resolve => unlock = resolve); locks.set(to, {promise, unlock}); }); func_(function unlock(to) { const ex = locks.get(to); if (!ex) return false; locks.delete(to); ex.unlock(); return true; }); func_(async function lockCall(fn, ...args) { await $.lock(fn); try { return await fn.call(this, ...args); } finally { $.unlock(fn); } }); func_(async function waitLocks() { while (locks.size) { const promises = Iter.values(locks).map('promise'); await Promise.all(promises); } }); const exitSignals = ['SIGINT', 'SIGTERM', 'SIGHUP']; let signalCaught = false; let signalBound = null; let originalProcessExit = null; const signalExitCodes = Object.assign(Object.create(null), { SIGHUP: 129, SIGINT: 130, SIGTERM: 143, }); $.onShutdownError = $.throw_('Error in shutdown'); const getSignalFunc = (msec, after = $.aecho, before = $.aecho) => async function (signal) { const code = typeof signal === 'number' ? signal : signalExitCodes[signal] || 2; if (!signalCaught) { signalCaught = true; const args = [signal, code, msec]; await Promise.race([ (async () => { await before.call(this, ...args); await $.waitLocks(); await after.call(this, ...args) })().catch($.onShutdownError), $.delayMsec(msec) ]); } $.delayShutdown(0); return process.exit(code); } func_(function delayShutdown(msec, {after, before} = {}) { if (originalProcessExit) { process.exit = originalProcessExit; originalProcessExit = null; } if (signalBound) { for (const signal of exitSignals) { process.off(signal, signalBound); } signalBound = null; } if (!(msec > 0)) return; originalProcessExit = process.exit; signalBound = getSignalFunc(msec, after, before); process.exit = signalBound; for (const signal of exitSignals) { process.on(signal, signalBound); } }); module.exports = $;