Skip to content

Instantly share code, notes, and snippets.

@NotIntMan
Last active February 22, 2016 20:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NotIntMan/eb86a06b951857b89478 to your computer and use it in GitHub Desktop.
Save NotIntMan/eb86a06b951857b89478 to your computer and use it in GitHub Desktop.
Promisify IPC

Promisify IPC

What is this?

This is Promise-style wrapper (or layer, if you want) for event-channel. Why did I write this? Because I need to get promised calls through IPC-channel.

P.S. IPC is channel, which implementing EventEmitter interface and gives us a way to communicate between node-processes

How to use

As you can see, it written on TypeScript. So, you need to compile it to JavaScript and just require that. Or import, as you want.

To use it in browser, you need to implement randomNumber function, which returns a Promise<number>.

Example

'use strict'

import PromisifyIPC from './promisify-ipc'
import {EventEmitter} from 'events'

// Let us wait for it
function delay(time): Promise<void> {
  return new Promise<void>(resolve=> {
    setTimeout(resolve, time)
  })
}

// Log function, log ALL results. Even errors.
function logPromises(arr: Array<Promise<any>>) {
  Promise.all(
    arr.map(
      p=> p.catch(
        err=> err
      )
    )
  )
  .then(res=> console.log(res))
}

// Let create ONE EventEmitter and TWO PromisifyIPC on it
let e = new EventEmitter
let p = new PromisifyIPC(e)
let n = new PromisifyIPC(e)

// And declare some handlers
p.on('myevent', a=> {
  return delay(1500).then(()=> a*2)
})

n.on('mult', (a,b)=> a*b)

p.on('length', function() {
  return arguments.length
})

// And now, let send some messages to out wrappers
// P.S. Since the layer uses the EventEmitter channel,
// there is no difference which object send a message
logPromises([
  n.send('myevent', 5),
  n.send('mult', 4, 5),
  p.send('mult', 10, 5),
  p.send('azaza', false),
  n.send('length', 1,2,3,3)
])

Out will be like this

[ 10, 20, 50, [Error: Timeout: Endpoint does not respond], 4 ]

API

class PromisifyIPC {
    constructor(emitter: NodeJS.EventEmitter);
    // Here everything is clear
    send(event: string, ...args: Array<any>): Promise<any>;
    // It sends your message to the channel
    // in use to EVENT event and returns promise
    on(event: string, handler: Function): void;
    // Add handler, which calls when EVENT event got a message
    off(event: string, fn: Function): void;
    // Simple. Removes a handler
}
'use strict'
import randomNumber from './random'
const TimeoutError = new Error('Timeout: Endpoint does not respond')
export default class PromisifyIPC {
functionCache: Map<string, WeakMap<Function, Function>>
emitter: NodeJS.EventEmitter
constructor(emitter: NodeJS.EventEmitter) {
this.emitter = emitter
this.functionCache = new Map
}
private _cache(event: string, fn: Function, def?: Function): Function {
let c: WeakMap<Function, Function>
if (this.functionCache.has(event))
c = this.functionCache.get(event)
else {
if (arguments.length > 2)
this.functionCache.set(event, c = new WeakMap)
else
return null
}
if (c.has(fn))
return c.get(fn)
else {
if (arguments.length > 2) {
c.set(fn, def)
return def
} else
return null
}
}
send(event: string, ...args: Array<any>): Promise<any>
send(event) {
let emitter = this.emitter
return randomNumber()
.then(_id=> {
let id = _id.toString()
return new Promise((resolve, reject)=> {
emitter.emit.apply(
emitter,
[event, id].concat(
Array.prototype.slice.call(arguments, 1)
)
)
emitter.once(event+'-response:'+id, (err, result)=> {
if (err)
reject(err)
else
resolve(result)
})
let timeout = setTimeout(()=> reject(TimeoutError), 3e3)
emitter.once(event+'-getit:'+id, ()=> clearTimeout(timeout))
})
})
}
on(event: string, handler: Function) {
let result
this.emitter.on(
event,
this._cache(
event,
handler,
function() {
let id = arguments[0].toString()
this.emitter.emit(event+'-getit:'+id)
Promise.resolve().then(()=> {
return handler.apply(null, Array.prototype.slice.call(arguments, 1))
})
.then(result=> this.emitter.emit(event+'-response:'+id, null, result))
.catch(error=> this.emitter.emit(event+'-response:'+id, error))
}.bind(this)
)
)
}
off(event: string, fn: Function) {
let f = this._cache(event, fn)
if (f !== null)
this.emitter.removeListener(event, f)
}
}
'use strict'
import * as Crypto from 'crypto'
export default function randomNumber(): Promise<number> {
return new Promise((resolve, reject)=> {
Crypto.randomBytes(6, (err, result)=> {
if (err) return reject(err)
resolve(result.readUIntBE(0, 6))
})
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment