├── .gitignore ├── Makefile ├── Readme.md ├── benchmarks └── index.js ├── examples ├── callbacks.js ├── fast-client.js ├── fast-server.js ├── hello-world.js ├── stress.js ├── thumb-client.js └── thumb-service.js ├── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | v8.log 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @./node_modules/.bin/mocha \ 4 | --require should \ 5 | --reporter dot \ 6 | --bail 7 | 8 | bench: 9 | @./node_modules/.bin/matcha \ 10 | benchmarks/index.js 11 | 12 | .PHONY: test bench -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # actorify 2 | 3 | Turn any duplex stream into an actor. Built on the the [AMP](https://github.com/visionmedia/node-amp) protocol 4 | for opaque binary and javascript argument support. 5 | 6 | Actors are similar to traditional RPC however they are isolated units of communication, an actor receives and sends zero or more messages to and from 7 | its peer with bi-directional messaging. Typical RPC is done at the process-level, 8 | meaning in order to work with data coupled with an identifier such as a user id 9 | that the id must be passed each request, whereas an actor may retain this state. 10 | 11 | ## Features 12 | 13 | - fast 14 | - clean api 15 | - json support 16 | - request timeouts 17 | - opaque binary support 18 | - simple flexible protocol 19 | - bi-directional messaging 20 | - request/response support 21 | 22 | ## Installation 23 | 24 | ``` 25 | $ npm install actorify 26 | ``` 27 | 28 | ## Guide 29 | 30 | ### Example 31 | 32 | Simple hello world PING/PONG example: 33 | 34 | ```js 35 | var net = require('net'); 36 | var actorify = require('actorify'); 37 | 38 | // server 39 | 40 | net.createServer(function(sock){ 41 | var actor = actorify(sock); 42 | 43 | actor.on('ping', function(){ 44 | console.log('PING'); 45 | actor.send('pong'); 46 | }); 47 | }).listen(3000); 48 | 49 | // client 50 | 51 | var sock = net.connect(3000); 52 | var actor = actorify(sock); 53 | 54 | setInterval(function(){ 55 | actor.send('ping'); 56 | actor.once('pong', function(){ 57 | console.log('PONG'); 58 | }); 59 | }, 300); 60 | ``` 61 | 62 | Sending a single request with multiple async responses, 63 | also illustrates how arguments may be primitives, json objects, 64 | or opaque binary such as sending an image over the wire for 65 | resizing, receiving multiple thumbnail blobs and their 66 | respective size: 67 | 68 | ```js 69 | var net = require('net'); 70 | var actorify = require('actorify'); 71 | 72 | // client 73 | 74 | net.createServer(function(sock){ 75 | var actor = actorify(sock); 76 | 77 | var img = Buffer.from('faux data'); 78 | 79 | actor.on('image thumbnails', function(img, sizes){ 80 | console.log('%s byte image -> %s', img.length, sizes.join(', ')); 81 | sizes.forEach(function(size){ 82 | actor.send('thumb', size, Buffer.from('thumb data')); 83 | }); 84 | }); 85 | }).listen(3000); 86 | 87 | // client 88 | 89 | setInterval(function(){ 90 | var sock = net.connect(3000); 91 | var actor = actorify(sock); 92 | 93 | console.log('send image for thumbs'); 94 | var img = Buffer.from('faux image'); 95 | actor.send('image thumbnails', img, ['150x150', '300x300']); 96 | 97 | actor.on('thumb', function(size, img){ 98 | console.log('thumb: %s', size); 99 | }); 100 | }, 500); 101 | ``` 102 | 103 | You may also associate callbacks with an actor message, effectively 104 | turning it into a traditional RPC call: 105 | 106 | ```js 107 | actor.send('get user', 'tobi', function(err, user){ 108 | 109 | }); 110 | 111 | actor.on('get user', function(name, reply){ 112 | getUser(name, function(err, user){ 113 | reply(err, user); 114 | }); 115 | }); 116 | ``` 117 | 118 | ### Timeouts 119 | 120 | When performing a request you may optionally timeout the response, 121 | after which an `Error` will be passed to the callback and any subsequent 122 | response will be ignored. 123 | 124 | The argument may be numeric milliseconds or represented as a string such 125 | as "5s", "10m", "1 minute", "30 seconds", etc. By default there is __no__ 126 | timeout. 127 | 128 | ```js 129 | actor.send('hello', function(err, res){ 130 | 131 | }).timeout('5s'); 132 | ``` 133 | 134 | ### Error Handling 135 | 136 | Stream errors are _not_ handled, you must add an "error" listener 137 | to the `stream` passed to actorify(). 138 | 139 | ## Benchmarks 140 | 141 | Benchmarks on my first generation MBP Retina with node 0.11.x. 142 | Real results are likely higher since having the 143 | producer on the same machine as the consumer makes 144 | results misleading. 145 | 146 | With __64b__ messages: 147 | 148 | ``` 149 | min: 56,818 ops/s 150 | mean: 159,207 ops/s 151 | median: 138,888 ops/s 152 | total: 1,376,188 ops in 8.644s 153 | through: 1.52 mb/s 154 | ``` 155 | 156 | With __1kb__ messages: 157 | 158 | ``` 159 | min: 56,179 ops/s 160 | mean: 153,919 ops/s 161 | median: 142,857 ops/s 162 | total: 909,974 ops in 5.912s 163 | through: 150.31 mb/s 164 | ``` 165 | 166 | With __10kb__ messages: 167 | 168 | ``` 169 | min: 11,389 ops/s 170 | mean: 64,167 ops/s 171 | median: 64,102 ops/s 172 | total: 352,025 ops in 5.486s 173 | through: 626.64 mb/s 174 | ``` 175 | 176 | With __32kb__ messages: 177 | 178 | ``` 179 | min: 7,032 ops/s 180 | mean: 14,269 ops/s 181 | median: 23,584 ops/s 182 | total: 86,329 ops in 6.05s 183 | through: 445.91 mb/s 184 | ``` 185 | 186 | 187 | # License 188 | 189 | MIT 190 | 191 | -------------------------------------------------------------------------------- /benchmarks/index.js: -------------------------------------------------------------------------------- 1 | 2 | var actorify = require('..'); 3 | var net = require('net'); 4 | 5 | // server 6 | 7 | net.createServer(function(sock){ 8 | var actor = actorify(sock); 9 | 10 | actor.on('ping', function(){ 11 | actor.send('pong'); 12 | }); 13 | 14 | actor.on('marco', function(reply){ 15 | reply(null, 'polo'); 16 | }); 17 | }).listen(4000); 18 | 19 | // benchmarks 20 | 21 | var sock = net.connect(4000); 22 | var actor = actorify(sock); 23 | 24 | suite('actorify', function(){ 25 | bench('.send()', function(next){ 26 | actor.send('ping'); 27 | actor.once('pong', next); 28 | }) 29 | 30 | bench('.send() callback', function(next){ 31 | actor.send('marco', next); 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /examples/callbacks.js: -------------------------------------------------------------------------------- 1 | 2 | var net = require('net'); 3 | var actorify = require('..'); 4 | 5 | // server 6 | 7 | net.createServer(function(sock){ 8 | var actor = actorify(sock); 9 | 10 | actor.on('uppercase', function(str, reply){ 11 | reply(null, str.toUpperCase()); 12 | }); 13 | 14 | actor.on('reverse', function(str, reply){ 15 | reply(null, str.split('').reverse().join('')); 16 | }); 17 | }).listen(3000); 18 | 19 | // client 20 | 21 | setInterval(function(){ 22 | var actor = actorify(net.connect(3000)); 23 | 24 | actor.send('uppercase', 'hello', function(err, str){ 25 | if (err) throw err; 26 | console.log('-> %s', str); 27 | }); 28 | 29 | actor.send('uppercase', 'world', function(err, str){ 30 | if (err) throw err; 31 | console.log('-> %s', str); 32 | }); 33 | 34 | actor.send('reverse', 'hey', function(err, str){ 35 | if (err) throw err; 36 | console.log('-> %s', str); 37 | }); 38 | }, 200); 39 | 40 | -------------------------------------------------------------------------------- /examples/fast-client.js: -------------------------------------------------------------------------------- 1 | 2 | var net = require('net'); 3 | var actorify = require('..'); 4 | 5 | var sock = net.connect(3000, 'localhost'); 6 | var actor = actorify(sock); 7 | var msg = Buffer.from(Array(64).join('a')); 8 | 9 | function next() { 10 | var n = 50; 11 | while (n--) actor.send('msg', msg); 12 | setImmediate(next); 13 | } 14 | 15 | next(); 16 | -------------------------------------------------------------------------------- /examples/fast-server.js: -------------------------------------------------------------------------------- 1 | 2 | var actorify = require('..'); 3 | var humanize = require('humanize-number'); 4 | var net = require('net'); 5 | 6 | var n = 0; 7 | var ops = 5000; 8 | var bytes = 64; 9 | var prev = start = Date.now(); 10 | var results = []; 11 | 12 | console.log(); 13 | 14 | net.createServer(function(sock){ 15 | prev = start = Date.now(); 16 | 17 | var actor = actorify(sock); 18 | 19 | actor.on('msg', function(){ 20 | if (n++ % ops == 0) { 21 | var ms = Date.now() - prev; 22 | var sec = ms / 1000; 23 | var persec = ops / sec | 0; 24 | results.push(persec); 25 | process.stdout.write('\r [' + persec + ' ops/s] [' + n + ']'); 26 | prev = Date.now(); 27 | } 28 | }); 29 | }).listen(3000, 'localhost'); 30 | 31 | function sum(arr) { 32 | return arr.reduce(function(sum, n){ 33 | return sum + n; 34 | }); 35 | } 36 | 37 | function min(arr) { 38 | return arr.reduce(function(min, n){ 39 | return n < min 40 | ? n 41 | : min; 42 | }); 43 | } 44 | 45 | function median(arr) { 46 | arr = arr.sort(); 47 | return arr[arr.length / 2 | 0]; 48 | } 49 | 50 | function done(){ 51 | var ms = Date.now() - start; 52 | var avg = n / (ms / 1000); 53 | console.log('\n'); 54 | console.log(' min: %s ops/s', humanize(min(results))); 55 | console.log(' mean: %s ops/s', humanize(avg | 0)); 56 | console.log(' median: %s ops/s', humanize(median(results))); 57 | console.log(' total: %s ops in %ds', humanize(n), ms / 1000); 58 | console.log(' through: %d mb/s', ((avg * bytes) / 1024 / 1024).toFixed(2)); 59 | console.log(); 60 | process.exit(); 61 | } 62 | 63 | process.on('SIGINT', done); 64 | setTimeout(done, 10000); 65 | -------------------------------------------------------------------------------- /examples/hello-world.js: -------------------------------------------------------------------------------- 1 | 2 | var net = require('net'); 3 | var actorify = require('..'); 4 | 5 | // server 6 | 7 | net.createServer(function(sock){ 8 | var actor = actorify(sock); 9 | 10 | actor.on('ping', function(){ 11 | console.log('PING'); 12 | actor.send('pong'); 13 | }); 14 | }).listen(3000); 15 | 16 | // client 17 | 18 | var sock = net.connect(3000); 19 | var actor = actorify(sock); 20 | 21 | setInterval(function(){ 22 | actor.send('ping'); 23 | 24 | actor.once('pong', function(){ 25 | console.log('PONG'); 26 | }); 27 | }, 300); -------------------------------------------------------------------------------- /examples/stress.js: -------------------------------------------------------------------------------- 1 | 2 | var net = require('net'); 3 | var actorify = require('..'); 4 | 5 | // server 6 | 7 | net.createServer(function(sock){ 8 | var actor = actorify(sock); 9 | 10 | actor.on('echo', function(str){ 11 | actor.send('result', str); 12 | }); 13 | }).listen(3000); 14 | 15 | // client 16 | 17 | var actor = actorify(net.connect(3000)); 18 | 19 | function next() { 20 | var n = 50; 21 | 22 | while (n--) actor.send('echo', 'hello'); 23 | 24 | setImmediate(next); 25 | } 26 | 27 | next(); 28 | -------------------------------------------------------------------------------- /examples/thumb-client.js: -------------------------------------------------------------------------------- 1 | 2 | var net = require('net'); 3 | var actorify = require('..'); 4 | 5 | setInterval(function(){ 6 | var sock = net.connect(3000); 7 | var actor = actorify(sock); 8 | 9 | console.log('send image for thumbs'); 10 | var img = Buffer.from('faux image'); 11 | actor.send('image thumbnails', img, ['150x150', '300x300']); 12 | 13 | actor.on('thumb', function(size, img){ 14 | console.log('thumb: %s', size); 15 | }); 16 | }, 500); 17 | -------------------------------------------------------------------------------- /examples/thumb-service.js: -------------------------------------------------------------------------------- 1 | 2 | var net = require('net'); 3 | var actorify = require('..'); 4 | 5 | net.createServer(function(sock){ 6 | var actor = actorify(sock); 7 | 8 | var img = Buffer.from('faux data'); 9 | 10 | actor.on('image thumbnails', function(img, sizes){ 11 | console.log('%s byte image -> %s', img.length, sizes.join(', ')); 12 | sizes.forEach(function(size){ 13 | actor.send('thumb', size, Buffer.from('thumb data')); 14 | }); 15 | }); 16 | }).listen(3000); 17 | 18 | console.log('server listening on 3000'); 19 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var Emitter = require('events').EventEmitter; 7 | var Message = require('amp-message'); 8 | var fmt = require('util').format; 9 | var parse = require('ms'); 10 | var amp = require('amp'); 11 | 12 | /** 13 | * Slice ref. 14 | */ 15 | 16 | var slice = [].slice; 17 | 18 | /** 19 | * Actor ids. 20 | */ 21 | 22 | var ids = 0; 23 | 24 | /** 25 | * Expose `Actor`. 26 | */ 27 | 28 | module.exports = Actor; 29 | 30 | /** 31 | * Initialize an actor for the given `Stream`. 32 | * 33 | * @param {Stream} stream 34 | * @api public 35 | */ 36 | 37 | function Actor(stream) { 38 | if (!(this instanceof Actor)) return new Actor(stream); 39 | this.parser = new amp.Stream; 40 | this.parser.on('data', this.onmessage.bind(this)); 41 | stream.pipe(this.parser); 42 | this.stream = stream; 43 | this.callbacks = {}; 44 | this.ids = 0; 45 | this.id = ++ids; 46 | Actor.emit('actor', this); 47 | } 48 | 49 | /** 50 | * Inherit from `Emitter.prototype`. 51 | */ 52 | 53 | Actor.prototype.__proto__ = Emitter.prototype; 54 | Actor.__proto__ = Emitter.prototype; 55 | 56 | /** 57 | * Inspect implementation. 58 | */ 59 | 60 | Actor.prototype.inspect = function(){ 61 | var cbs = Object.keys(this.callbacks).length; 62 | return fmt('', this.id, cbs); 63 | }; 64 | 65 | /** 66 | * Handle message. 67 | */ 68 | 69 | Actor.prototype.onmessage = function(buf){ 70 | var msg = new Message(buf); 71 | var args = msg.args; 72 | var self = this; 73 | 74 | // reply message, invoke 75 | // the given callback 76 | if ('_reply_' == args[0]) { 77 | args.shift(); 78 | var id = args.shift().toString(); 79 | var fn = this.callbacks[id]; 80 | delete this.callbacks[id]; 81 | if (fn) fn.apply(null, args); 82 | return; 83 | } 84 | 85 | // request method, pass 86 | // a trailing callback 87 | if (isId(args[0])) { 88 | var id = args.shift(); 89 | args.push(function(){ 90 | self.send.apply(self, reply(id, arguments)); 91 | }); 92 | } 93 | 94 | this.emit.apply(this, args); 95 | }; 96 | 97 | /** 98 | * Send message. 99 | * 100 | * TODO: clean up... return a Message etc 101 | * 102 | * @param {String} msg 103 | * @param {Mixed} ... 104 | * @return {Object} 105 | * @api public 106 | */ 107 | 108 | Actor.prototype.send = function(){ 109 | if ('string' != typeof arguments[0]) throw new Error('missing message name'); 110 | var args = slice.call(arguments); 111 | var last = args[args.length - 1]; 112 | var timer; 113 | 114 | if ('function' == typeof last) { 115 | var id = 'i:' + this.ids++; 116 | var fn = args.pop(); 117 | 118 | function callback(){ 119 | callback = function(){}; 120 | clearTimeout(timer); 121 | fn.apply(this, arguments); 122 | } 123 | 124 | this.callbacks[id] = callback; 125 | args.unshift(Buffer.from(id)); 126 | } 127 | 128 | var msg = new Message(args); 129 | this.stream.write(msg.toBuffer()); 130 | 131 | return { 132 | timeout: function(ms){ 133 | if ('string' == typeof ms) ms = parse(ms); 134 | timer = setTimeout(function(){ 135 | var err = new Error('message response timeout exceeded'); 136 | err.timeout = ms; 137 | callback(err); 138 | }, ms); 139 | } 140 | } 141 | }; 142 | 143 | /** 144 | * Return a reply message for `id` and `args`. 145 | * 146 | * @param {String} id 147 | * @param {Array} args 148 | * @return {Array} 149 | * @api private 150 | */ 151 | 152 | function reply(id, args) { 153 | var msg = new Array(2 + args.length); 154 | 155 | msg[0] = '_reply_'; 156 | msg[1] = id; 157 | 158 | for (var i = 0; i < args.length; i++) { 159 | msg[i + 2] = args[i]; 160 | } 161 | 162 | return msg; 163 | } 164 | 165 | /** 166 | * ID argument. 167 | */ 168 | 169 | function isId(arg) { 170 | return 105 == arg[0] && 58 == arg[1]; 171 | } 172 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "actorify", 3 | "version": "1.0.0", 4 | "repository": "visionmedia/node-actorify", 5 | "description": "Turn any duplex stream into an actor", 6 | "keywords": [ 7 | "actor", 8 | "messaging", 9 | "message", 10 | "zmq", 11 | "zeromq", 12 | "erlang" 13 | ], 14 | "dependencies": { 15 | "amp": "0.3.1", 16 | "ms": "~0.6.1", 17 | "amp-message": "0.1.1" 18 | }, 19 | "devDependencies": { 20 | "mocha": "*", 21 | "should": "*", 22 | "matcha": "~0.4.0", 23 | "humanize-number": "0.0.1" 24 | }, 25 | "license": "MIT" 26 | } 27 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | var Stream = require('stream').PassThrough; 3 | var assert = require('assert'); 4 | var actorify = require('..'); 5 | var net = require('net'); 6 | 7 | describe('Actor', function(){ 8 | it('should emit "actor" for visibility', function(done){ 9 | actorify.once('actor', function(actor){ 10 | assert('Actor' == actor.constructor.name); 11 | done(); 12 | }); 13 | 14 | var stream = new Stream; 15 | var actor = actorify(stream); 16 | }) 17 | }) 18 | 19 | describe('Actor#inspect()', function(){ 20 | it('should inspect the actor', function(){ 21 | var stream = new Stream; 22 | var actor = actorify(stream); 23 | actor.inspect().should.equal(''); 24 | }) 25 | }) 26 | 27 | describe('Actor#send()', function(){ 28 | describe('with no message name', function(){ 29 | it('should throw an error', function(done){ 30 | var stream = new Stream; 31 | var actor = actorify(stream); 32 | 33 | try { 34 | actor.send(); 35 | } catch (err) { 36 | err.message.should.equal('missing message name'); 37 | done(); 38 | } 39 | }) 40 | }) 41 | 42 | describe('with no arguments', function(){ 43 | it('should emit nothing', function(done){ 44 | var stream = new Stream; 45 | var actor = actorify(stream); 46 | 47 | actor.on('hello', function(){ 48 | done(); 49 | }); 50 | 51 | actor.send('hello'); 52 | }) 53 | }) 54 | 55 | describe('with many arguments', function(){ 56 | it('should emit all arguments', function(done){ 57 | var stream = new Stream; 58 | var actor = actorify(stream); 59 | 60 | actor.on('thumb', function(size, img){ 61 | size.should.equal('150x150'); 62 | img.toString().should.equal('data'); 63 | done(); 64 | }); 65 | 66 | actor.send('thumb', '150x150', Buffer.from('data')); 67 | }) 68 | }) 69 | 70 | describe('with a callback', function(){ 71 | it('should map responses back', function(done){ 72 | // server 73 | net.createServer(function(sock){ 74 | var actor = actorify(sock); 75 | 76 | actor.on('reverse', function(str, reply){ 77 | reply(null, str.split('').reverse().join('')); 78 | }); 79 | }).listen(4040); 80 | 81 | // client 82 | var actor = actorify(net.connect(4040)); 83 | 84 | actor.send('reverse', 'hey', function(err, str){ 85 | if (err) throw err; 86 | str.should.equal('yeh'); 87 | done(); 88 | }); 89 | }) 90 | 91 | it('should work with no args', function(done){ 92 | // server 93 | net.createServer(function(sock){ 94 | var actor = actorify(sock); 95 | 96 | actor.on('ping', function(reply){ 97 | reply(null, 'pong'); 98 | }); 99 | }).listen(4050); 100 | 101 | // client 102 | var actor = actorify(net.connect(4050)); 103 | 104 | actor.send('ping', function(err, str){ 105 | if (err) throw err; 106 | str.should.equal('pong'); 107 | done(); 108 | }); 109 | }) 110 | }) 111 | 112 | describe('with strings', function(){ 113 | it('should pass strings', function(done){ 114 | var stream = new Stream; 115 | var actor = actorify(stream); 116 | 117 | actor.on('hello', function(str){ 118 | str.should.equal('world'); 119 | done(); 120 | }); 121 | 122 | actor.send('hello', 'world'); 123 | }) 124 | }) 125 | 126 | describe('with objects', function(){ 127 | it('should pass objects', function(done){ 128 | var stream = new Stream; 129 | var actor = actorify(stream); 130 | 131 | actor.on('hello', function(obj){ 132 | obj.foo.should.equal('bar'); 133 | obj.bar.should.equal('baz'); 134 | done(); 135 | }); 136 | 137 | actor.send('hello', { foo: 'bar', bar: 'baz' }); 138 | }) 139 | }) 140 | 141 | describe('with buffers', function(){ 142 | it('should pass buffers', function(done){ 143 | var stream = new Stream; 144 | var actor = actorify(stream); 145 | 146 | actor.on('hello', function(buf){ 147 | assert(Buffer.isBuffer(buf)); 148 | buf.toString().should.equal('world'); 149 | done(); 150 | }); 151 | 152 | actor.send('hello', Buffer.from('world')); 153 | }) 154 | }) 155 | 156 | describe('with a timeout', function(){ 157 | describe('when exceeded', function(){ 158 | it('should pass an error', function(done){ 159 | var stream = new Stream; 160 | var actor = actorify(stream); 161 | 162 | actor.on('hello', function(fn){ 163 | 164 | }); 165 | 166 | actor.send('hello', function(err){ 167 | err.timeout.should.equal(50); 168 | err.message.should.equal('message response timeout exceeded'); 169 | done(); 170 | }).timeout(50); 171 | }) 172 | 173 | it('should ignore responses', function(done){ 174 | var stream = new Stream; 175 | var actor = actorify(stream); 176 | 177 | actor.on('hello', function(fn){ 178 | setTimeout(function(){ 179 | fn(null, 'world'); 180 | }, 100); 181 | }); 182 | 183 | actor.send('hello', function(err){ 184 | err.timeout.should.equal(50); 185 | err.message.should.equal('message response timeout exceeded'); 186 | done(); 187 | }).timeout(50); 188 | }) 189 | }) 190 | 191 | describe('otherwise', function(){ 192 | it('should cancel the timeout', function(done){ 193 | var stream = new Stream; 194 | var actor = actorify(stream); 195 | 196 | actor.on('hello', function(fn){ 197 | setTimeout(function(){ 198 | fn(null, 'world'); 199 | }, 10); 200 | }); 201 | 202 | actor.send('hello', function(err, res){ 203 | assert(!err); 204 | res.should.equal('world'); 205 | done(); 206 | }).timeout(150); 207 | }) 208 | }) 209 | }) 210 | 211 | it('should support multiple messages', function(done){ 212 | var stream = new Stream; 213 | var actor = actorify(stream); 214 | 215 | var n = 0; 216 | 217 | actor.on('thumb', function(size){ 218 | switch (n++) { 219 | case 0: 220 | size.should.equal('150x150'); 221 | break; 222 | 223 | case 1: 224 | size.should.equal('300x300'); 225 | break; 226 | 227 | case 2: 228 | size.should.equal('600x600'); 229 | done(); 230 | break; 231 | } 232 | }); 233 | 234 | actor.send('thumb', '150x150'); 235 | actor.send('thumb', '300x300'); 236 | actor.send('thumb', '600x600'); 237 | }) 238 | }) 239 | --------------------------------------------------------------------------------