process-worker-executor
Version:
将多进程和多线程封装为统一的 Async/Await 调用方式, 简化 IPC 消息通信的复杂性
224 lines (222 loc) • 7.17 kB
JavaScript
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