UNPKG

process-worker-executor

Version:

将多进程和多线程封装为统一的 Async/Await 调用方式, 简化 IPC 消息通信的复杂性

224 lines (222 loc) 7.17 kB
const _ = require('lodash') const log = require('../utils/logger') class Executor { #id = 1 #cache = new Map() disposable = [] #isNotInit = true ttl = -1 #ttlTimer logger logPath logFile name constructor(opts) { this.name = opts.name this.ttl = opts.ttl || this.ttl this.logPath = opts.logPath this.logFile = opts.logFile if (this.logPath && this.logFile) { const name = this.logFile + '.txt' this.logger = log.create(this.logPath, name) this.disposable.push(() => { log.close(name) }) } } #refreshTimeToLive() { clearTimeout(this.#ttlTimer) if (this.ttl > 0) { this.#ttlTimer = setTimeout(() => { if (this.#cache.size > 0) { this.logger?.info('尝试关闭执行器失败, 等待队列任务完成...') this.#refreshTimeToLive() } else { this.close() } }, this.ttl) } } #createProxyFn(target, prop, metedata) { let originalFn = target[prop] let max = originalFn.length return async (...args) => { let stime = Date.now() let isAsync try { if (args.length <= max) { return await originalFn.call(target, ...args) } isAsync = args.at(max) if (isAsync) { const available = args.slice(0, max) return await this.send(metedata, target, prop, ...available) } else { return await originalFn.call(target, ...args) } } finally { const duration = Date.now() - stime + ' ms' this.logger?.info({ isAsync, prop, args, duration }) } } } #invokeMethodProxy(that, fn, metedata, args) { let max = fn.length let stime = Date.now() let isAsync try { if (args.length <= max) { return fn.apply(that, ...args) } isAsync = args.at(max) if (isAsync) { const available = args.slice(0, max) return this.send(metedata, that, '', ...available) } else { return fn.apply(that, ...args) } } finally { const duration = Date.now() - stime + ' ms' this.logger?.info({ isAsync, args, duration }) } } createMessage(metedata, target, command, ...args) { const commandId = this.#id++ const useds = this.#encodeArgs(target, args) const data = { id: commandId, command, data: useds, metedata } return data } clearMessage(id) { const item = this.#cache.get(id) const args = item.data args.forEach(it => { this.#cache.delete(it.id) }) this.#cache.delete(id) } #encodeArgs(target, args) { return args.map(it => { if (_.isFunction(it)) { const id = this.#id++ this.#cache.set(id, { fn: it, target }) return { id, type: 'FUNCTION' } } return it }) } #decodeReturn(response) { if (!response) { return } if (response.type == 'FUNCTION') { return async (...args) => { return await this.send({}, response, 'RETURN_FUNCTION', value.id, ...args) } } if (_.isPlainObject(response)) { return _.mapValues(response, value => { if (value.type == 'FUNCTION') { return async (...args) => { return await this.send({}, response, 'RETURN_FUNCTION', value.id, ...args) } } return value }) } return response } async send(metedata, target, prop, ...args) { if (this.#isNotInit) { this.#isNotInit = false await this.onInit() } const msg = this.createMessage(metedata, target, prop, ...args) return new Promise((resolve, reject) => { const command = Object.assign({}, msg) msg.resolve = resolve msg.reject = reject this.#cache.set(msg.id, msg) this.#refreshTimeToLive() return this.onSend(command) }) } on(message) { const { id, command, data } = message const item = this.#cache.get(id) switch (command) { case 'ARGS_FUNCTION': { item.fn?.(...data) } break case 'ERROR': { const processError = new Error(data.message) processError.stack = data.stack item.reject(processError) this.clearMessage(id) } break case 'FINISH': { const response = this.#decodeReturn(data) item.resolve(response) this.clearMessage(id) } break } } async close() { if (!this.#isNotInit) { await this.send({}, {}, 'CLOSE') } clearTimeout(this.#ttlTimer) await this.logger?.info('执行器已经关闭, 资源已经释放...') for (const dispose of this.disposable) { await dispose() } this.#isNotInit = true } create(target, predicat, metedata) { const handler = { get: (target, prop, receiver) => { const fn = target[prop] if (prop == '_executor') { return this } if (prop == '_dispose') { return () => { return this.close() } } if (predicat(prop, fn)) { return this.#createProxyFn(target, prop, metedata) } else { return Reflect.get(target, prop, receiver) } }, apply: (fn, that, args) => { if (predicat('', fn)) { return this.#invokeMethodProxy(that, fn, metedata, args) } else { return fn.apply(that, args) } } } return new Proxy(target, handler) } async onInit() {} async onSend(data) {} } module.exports = Executor