├── .npmignore ├── .gitignore ├── src ├── ops.js ├── buffers.js ├── channel.js └── index.js ├── gulpfile.js ├── examples ├── daisy.js ├── pingpong.js ├── fanIn.js └── fakesearch.js ├── package.json ├── README.md ├── LICENSE └── test ├── tutorial.js └── tests.js /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | examples -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | .idea 4 | *.iml 5 | -------------------------------------------------------------------------------- /src/ops.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export class Put { 4 | constructor(ch, val) { 5 | this.ch = ch 6 | this.val = val 7 | } 8 | } 9 | 10 | export class Race { 11 | constructor(ops) { 12 | this.ops = ops 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var gulp = require('gulp') 4 | var babel = require('gulp-babel') 5 | 6 | gulp.task('default', function() { 7 | return gulp.src('src/*.js') 8 | .pipe(babel()) 9 | .pipe(gulp.dest('build')) 10 | }) 11 | -------------------------------------------------------------------------------- /examples/daisy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // http://talks.golang.org/2012/concurrency.slide#39 4 | // Daisy-chain 5 | const pogo = require("../index.js"); 6 | 7 | function* chain(left, right) { 8 | yield pogo.put(left, 1 + (yield right)); 9 | } 10 | 11 | pogo(function*() { 12 | const n = 100000; 13 | const leftmost = pogo.chan(); 14 | let right = leftmost; 15 | let left = leftmost; 16 | 17 | // Start the goroutines 18 | for (let i = 0; i < n; i++) { 19 | right = pogo.chan(); 20 | pogo(chain, [left, right]); 21 | left = right; 22 | } 23 | 24 | // Start the chain 25 | pogo(function*() { 26 | yield pogo.put(right, 1); 27 | }); 28 | 29 | console.log((yield leftmost)); 30 | }); 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "po-go", 3 | "version": "0.0.12", 4 | "author": "Alan O'Donnell ", 5 | "description": "ES6 generators, promises, and CSP channels.", 6 | "keywords": [ "es6", "generators", "promises", "csp" ], 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/happy4crazy/po-go" 10 | }, 11 | "main": "build/index.js", 12 | "devDependencies": { 13 | "chai": "*", 14 | "mocha": "*", 15 | "babel": "*", 16 | "babel-runtime": "*", 17 | "gulp": "*", 18 | "gulp-babel": "*" 19 | }, 20 | "scripts": { 21 | "test": "mocha --compilers js:babel/register", 22 | "prepublish": "gulp" 23 | }, 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ```javascript 2 | import pogo, { chan, put } from './index.js' 3 | 4 | const sleep = ms => new Promise(awaken => setTimeout(awaken, ms)) 5 | 6 | function* player(name, table) { 7 | while (true) { 8 | const ball = yield table 9 | console.log(`ball: ${ball}`) 10 | if (ball === "deflated") { 11 | console.log(`${name}: the ball popped :(`) 12 | return 13 | } 14 | ball.hits += 1 15 | yield sleep(100) 16 | yield put(table, ball) 17 | } 18 | } 19 | 20 | pogo(function* () { 21 | const table = chan() 22 | 23 | pogo(player(['ping', table])).catch(e => console.log('ping wtf:', e)) 24 | pogo(player(['pong', table])).catch(e => console.log('pong wtf:', e)) 25 | 26 | yield put(table, {hits: 0}) 27 | yield sleep(1000) 28 | yield put(table, 'deflated') 29 | }).catch(e => console.log('game wtf:', e)); 30 | ``` 31 | -------------------------------------------------------------------------------- /examples/pingpong.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const pogo = require('../index.js'), 4 | chan = pogo.chan, 5 | put = pogo.put, 6 | sleep = ms => new Promise(yep => setTimeout(yep, ms)); 7 | 8 | function* player(name, table) { 9 | while (true) { 10 | const ball = yield table; 11 | console.log("ball:", ball); 12 | if (ball === "deflated") { 13 | console.log(name + ": the ball popped :("); 14 | return; 15 | } 16 | ball.hits += 1; 17 | yield sleep(100); 18 | yield put(table, ball); 19 | } 20 | } 21 | 22 | pogo(function* () { 23 | const table = chan(); 24 | 25 | pogo(player, ["ping", table]).catch(e => console.log("ping wtf:", e)); 26 | pogo(player, ["pong", table]).catch(e => console.log("pong wtf:", e)); 27 | 28 | yield put(table, {hits: 0}); 29 | yield sleep(1000); 30 | yield put(table, "deflated"); 31 | }).catch(e => console.log("game wtf:", e)); 32 | -------------------------------------------------------------------------------- /src/buffers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export class StrictBuffer { 4 | constructor(capacity) { 5 | this.capacity = capacity 6 | this.buf = [] 7 | } 8 | 9 | empty() { return this.buf.length === 0 } 10 | 11 | add(val) { 12 | if (this.buf.length === this.capacity) { return false } 13 | 14 | this.buf.push(val) 15 | return true 16 | } 17 | remove() { return this.buf.shift() } 18 | } 19 | 20 | export class RingBuffer { 21 | constructor(capacity) { 22 | this.capacity = capacity 23 | this.buf = [] 24 | this.offset = 0 25 | } 26 | 27 | empty() { return this.buf[this.offset] === undefined } 28 | 29 | add(val) { 30 | this.buf[this.offset] = val 31 | this.offset = (this.offset + 1) % this.capacity 32 | return true 33 | } 34 | remove() { 35 | const ret = this.buf[this.offset] 36 | this.offset = (this.offset - 1) % this.capacity 37 | return ret 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/fanIn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const pogo = require('../index.js'); 4 | const wtf = e => console.log("wtf", e); 5 | 6 | 7 | const sleep = ms => new Promise(yep => setTimeout(yep, ms)); 8 | 9 | function boring(msg) { 10 | const ch = pogo.chan(); 11 | pogo(function*() { 12 | for (let i = 0;; i++) { 13 | yield pogo.put(ch, msg + " " + i); 14 | yield sleep(Math.random() * 1000); 15 | } 16 | }).catch(e => console.log("boring wtf", e)); 17 | return ch; 18 | } 19 | 20 | function fanIn(in1, in2) { 21 | const ch = pogo.chan(); 22 | pogo(function*() { 23 | for (;;) { 24 | const r = yield pogo.race([in1, in2]); 25 | yield pogo.put(ch, r.value); 26 | } 27 | }).catch(e => console.log("fanIn wtf", e)); 28 | return ch; 29 | } 30 | 31 | pogo(function*() { 32 | const ch = fanIn(boring("sup"), boring("yo")); 33 | for (let i = 0; i < 10; i++) { 34 | console.log(yield ch); 35 | } 36 | }).catch(e => console.log("main wtf", e)); 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2015 Alan O'Donnell <alan.m.odonnell@gmail.com> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /examples/fakesearch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const pogo = require("../index.js"), 4 | race = pogo.race, 5 | chan = pogo.chan, 6 | put = pogo.put; 7 | 8 | const sleep = ms => new Promise((yep, nope) => setTimeout(yep, ms)); 9 | 10 | function fakeSearch(kind) { 11 | return function*(query) { 12 | yield sleep(Math.random() * 200); 13 | return kind + " result for query " + query; 14 | }; 15 | } 16 | 17 | var web1 = fakeSearch("web1"); 18 | var web2 = fakeSearch("web2"); 19 | var image1 = fakeSearch("image1"); 20 | var image2 = fakeSearch("image2"); 21 | var video1 = fakeSearch("video1"); 22 | var video2 = fakeSearch("video2"); 23 | 24 | function* first(query, replicas) { 25 | const ch = chan(); 26 | function* searchReplica(i) { 27 | yield put(ch, (yield* replicas[i](query))); 28 | } 29 | for (var i = 0; i < replicas.length; i++) { 30 | pogo(searchReplica, [i]).catch(e => console.log("wtf", e)); 31 | } 32 | return (yield ch); 33 | } 34 | 35 | function* google(query) { 36 | var ch = chan(); 37 | 38 | pogo(function*() { 39 | yield put(ch, (yield* first(query, [web1, web2]))); 40 | }).catch(e => console.log("wtf", e)); 41 | pogo(function*() { 42 | yield put(ch, (yield* first(query, [image1, image2]))); 43 | }).catch(e => console.log("wtf", e)); 44 | pogo(function*() { 45 | yield put(ch, (yield* first(query, [video1, video2]))); 46 | }).catch(e => console.log("wtf", e)); 47 | 48 | var t = sleep(80); 49 | 50 | var results = []; 51 | for (var i = 0; i < 3; i++) { 52 | var r = yield race([ch, t.then(() => "zzz")]); 53 | if (r.channel) { 54 | results.push(r.value); 55 | } else { 56 | console.log("timed out"); 57 | break; 58 | } 59 | } 60 | 61 | return results; 62 | } 63 | 64 | pogo(function*() { 65 | var start = new Date(); 66 | var results = yield* google("PLT"); 67 | var elapsed = new Date() - start; 68 | console.log(results.join("\n")); 69 | console.log(elapsed); 70 | }).catch(e => console.log("wtf", e)); 71 | -------------------------------------------------------------------------------- /src/channel.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { StrictBuffer } from './buffers' 4 | 5 | export default class Channel { 6 | constructor(buf) { 7 | this.pendingTakings = [] 8 | this.pendingPutings = [] 9 | this.bufferedPutings = buf || new StrictBuffer(0) 10 | } 11 | 12 | _take(taker, ok, notOk) { 13 | if (taker.finished) { notOk(); return } 14 | 15 | this._disqualifySlowRacers() 16 | 17 | const bufferedPuting = this.bufferedPutings.remove() 18 | if (bufferedPuting) { 19 | if (bufferedPuting.puter.finished !== undefined) { 20 | bufferedPuting.puter.finished = true 21 | } 22 | bufferedPuting.ok() 23 | 24 | const pendingPuting = this.pendingPutings.shift() 25 | if (pendingPuting) { 26 | this.bufferedPutings.add(pendingPuting) 27 | if (pendingPuting.puter.finished !== undefined) { 28 | pendingPuting.puter.finished = true 29 | } 30 | pendingPuting.ok() 31 | } 32 | 33 | ok(bufferedPuting.value) 34 | } else { 35 | const pendingPuting = this.pendingPutings.shift() 36 | if (pendingPuting) { 37 | if (pendingPuting.puter.finished !== undefined) { 38 | pendingPuting.puter.finished = true 39 | } 40 | pendingPuting.ok() 41 | 42 | if (taker.finished !== undefined) { 43 | taker.finished = true 44 | } 45 | ok(pendingPuting.value) 46 | } 47 | else { 48 | this.pendingTakings.push({taker, ok, notOk}) 49 | } 50 | } 51 | } 52 | 53 | _put(puter, value, ok, notOk) { 54 | if (puter.finished) { notOk(); return } 55 | 56 | this._disqualifySlowRacers() 57 | 58 | const pendingTaking = this.pendingTakings.shift() 59 | if (pendingTaking) { 60 | if (pendingTaking.taker.finished !== undefined) { 61 | pendingTaking.taker.finished = true 62 | } 63 | pendingTaking.ok(value) 64 | 65 | if (puter.finished !== undefined) { 66 | puter.finished = true 67 | } 68 | ok() 69 | } else { 70 | const puting = {puter, ok, notOk, value} 71 | if (this.bufferedPutings.add(puting)) { 72 | ok() 73 | } else { 74 | this.pendingPutings.push(puting) 75 | } 76 | } 77 | } 78 | 79 | put(puter, value) { return new Promise(this._put.bind(this, puter, value)) } 80 | putAsync(value, ok = noOp) { this._put('async', value, ok) } 81 | 82 | take(taker) { return new Promise(this._take.bind(this, taker)) } 83 | takeAsync(ok = noOp) { return this._take('async', ok) } 84 | 85 | _disqualifySlowRacers() { 86 | for (let t of this.pendingTakings) { 87 | if (t.taker.finished) { t.notOk() } 88 | } 89 | this.pendingTakings = this.pendingTakings.filter(t => !t.taker.finished) 90 | 91 | for (let p of this.pendingPutings) { 92 | if (p.puter.finished) { p.notOk() } 93 | } 94 | this.pendingPutings = this.pendingPutings.filter(p => !p.puter.finished) 95 | } 96 | } 97 | 98 | function noOp() {} 99 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import Channel from './channel' 4 | import { Put, Race } from './ops' 5 | import { StrictBuffer, RingBuffer } from './buffers' 6 | 7 | /* 8 | * pogo : *r -> promise r 9 | * pogo : ((...args -> *r), ...args) -> promise r 10 | */ 11 | export default function pogo(genOrStar, ...args) { 12 | const gen = isGen(genOrStar) ? genOrStar : genOrStar(...args) 13 | const cachedPromisifications = new WeakMap() 14 | return new Promise((ok, notOk) => { 15 | bounce() 16 | 17 | function bounce(input) { 18 | let output 19 | try { output = gen.next(input) } 20 | catch (e) { notOk(e); return } 21 | decode(output) 22 | } 23 | function toss(error) { 24 | let output 25 | try { output = gen.throw(error) } 26 | catch (e) { notOk(e); return } 27 | decode(output) 28 | } 29 | 30 | function decode(output) { 31 | if (output.done) { ok(output.value); return } 32 | 33 | const instr = output.value 34 | if (isPromise(instr)) { instr.then(bounce, toss); return } 35 | if (instr instanceof Channel) { instr.take(gen).then(bounce); return } 36 | if (instr instanceof Put) { instr.ch.put(gen, instr.val).then(bounce); return } 37 | if (instr instanceof Race) { 38 | const race = { finished: false } 39 | instr.ops.forEach(op => { 40 | if (isPromise(op)) { 41 | op.then(i => { if (!race.finished) { race.finished = true; bounce({winner: op, value: i}) } }, 42 | e => { if (!race.finished) { race.finished = true; toss(e) } }) 43 | } 44 | if (op instanceof Channel) { 45 | op.take(race).then(i => bounce({ winner: op, value: i })) 46 | } 47 | if (op instanceof Put) { 48 | op.ch.put(race, op.val).then(() => bounce({ winner: op })) 49 | } 50 | if (isGen(op)) { 51 | if (!cachedPromisifications.has(op)) { cachedPromisifications.set(op, pogo(op)) } 52 | cachedPromisifications.get(op).then(i => { 53 | if (!race.finished) { race.finished = true; bounce({winner: op, value: i}) } 54 | }, e => { 55 | if (!race.finished) { race.finished = true; toss(e) } 56 | }) 57 | } 58 | }) 59 | return 60 | } 61 | if (isGen(instr)) { 62 | if (!cachedPromisifications.has(instr)) { cachedPromisifications.set(instr, pogo(instr)) } 63 | cachedPromisifications.get(instr).then(bounce, toss) 64 | return 65 | } 66 | 67 | notOk(new Error(`Invalid yield instruction: ${instr}.`)) 68 | } 69 | }) 70 | } 71 | 72 | export const chan = buf => new Channel(buf) 73 | export const put = (ch, val) => new Put(ch, val) 74 | export const race = ops => new Race(ops) 75 | export const strictBuffer = capacity => new StrictBuffer(capacity) 76 | export const ringBuffer = capacity => new RingBuffer(capacity) 77 | 78 | function isPromise(x) { return typeof x.then === 'function' } 79 | function isGen(x) { 80 | return typeof x.next === 'function' && typeof x.throw === 'function' 81 | } 82 | -------------------------------------------------------------------------------- /test/tutorial.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { assert } from 'chai' 4 | 5 | describe('the simple version', () => { 6 | function pogo(star) { 7 | const gen = star() 8 | return new Promise(ok => { 9 | bounce() 10 | function bounce(input) { 11 | const output = gen.next(input) 12 | if (output.done) ok(output.value) 13 | else output.value.then(bounce) 14 | } 15 | }) 16 | } 17 | 18 | it('handles promises that resolve', () => { 19 | return pogo(function*() { 20 | const num = yield Promise.resolve(123) 21 | const str = yield Promise.resolve("foo") 22 | return [num, str] 23 | }).then(v => assert.deepEqual(v, [123, "foo"])) 24 | }) 25 | 26 | it("doesn't handle promises that reject though") 27 | it("doesn't handle pogos that throw exceptions either") 28 | }) 29 | 30 | describe('the slightly more complicated version', () => { 31 | function pogo(star) { 32 | const gen = star() 33 | return new Promise(ok => { 34 | bounce() 35 | function bounce(input) { 36 | const output = gen.next(input) 37 | if (output.done) ok(output.value) 38 | else output.value.then(bounce, toss) 39 | } 40 | function toss(err) { 41 | const output = gen.throw(err) 42 | if (output.done) ok(output.value) 43 | else output.value.then(bounce, toss) 44 | } 45 | }) 46 | } 47 | 48 | it('still handles promises that resolve', () => { 49 | return pogo(function*() { 50 | const num = yield Promise.resolve(123) 51 | const str = yield Promise.resolve("foo") 52 | return [num, str] 53 | }).then(v => assert.deepEqual(v, [123, "foo"])) 54 | }) 55 | 56 | it('now handles promises that reject', () => { 57 | return pogo(function*() { 58 | try { yield Promise.reject('bang!') } 59 | catch (e) { assert.equal(e, 'bang!') } 60 | }) 61 | }) 62 | 63 | it("still doesn't handle pogos that throw exceptions though") 64 | }) 65 | 66 | describe('the working version', () => { 67 | function pogo(star) { 68 | const gen = star() 69 | return new Promise(ok => { 70 | bounce() 71 | function bounce(input) { decode(gen.next(input)) } 72 | function toss(error) { decode(gen.throw(error)) } 73 | function decode(output) { 74 | if (output.done) ok(output.value) 75 | else output.value.then(bounce, toss) 76 | } 77 | }) 78 | } 79 | 80 | it('still handles promises that resolve', () => { 81 | return pogo(function*() { 82 | const num = yield Promise.resolve(123) 83 | const str = yield Promise.resolve("foo") 84 | return [num, str] 85 | }).then(v => assert.deepEqual(v, [123, "foo"])) 86 | }) 87 | 88 | it('still handles promises that reject', () => { 89 | return pogo(function*() { 90 | try { yield Promise.reject('bang!') } 91 | catch (e) { assert.equal(e, 'bang!') } 92 | }) 93 | }) 94 | 95 | it('now handles pogos that throw exceptions too', () => { 96 | return pogo(function*() { 97 | throw 'bang!' 98 | }).catch(e => assert.equal(e, 'bang!')) 99 | }) 100 | }) 101 | 102 | describe('the csp version', () => { 103 | class Channel { 104 | constructor() { 105 | this.putings = [] 106 | this.takings = [] 107 | } 108 | put(puter, value) { 109 | return new Promise(ok => { 110 | if (!this.takings.length) return this.putings.push({value, ok}) 111 | const taking = this.takings.shift() 112 | taking.ok(value) 113 | ok() 114 | }) 115 | } 116 | take(taker) { 117 | return new Promise(ok => { 118 | if (!this.putings.length) return this.takings.push({ok}) 119 | const puting = this.putings.shift() 120 | puting.ok() 121 | ok(puting.value) 122 | }) 123 | } 124 | } 125 | const chan = () => new Channel 126 | 127 | class Put { 128 | constructor(channel, value) { 129 | this.channel = channel; 130 | this.value = value; 131 | } 132 | } 133 | const put = (ch, val) => new Put(ch, val) 134 | 135 | class Race { 136 | constructor(ops) { 137 | this.ops = ops 138 | } 139 | } 140 | 141 | const isPromise = x => typeof x.then === 'function' && typeof x.throw === 'function' 142 | 143 | function pogo(star) { 144 | const gen = star() 145 | return new Promise(ok => { 146 | bounce() 147 | function bounce(input) { decode(gen.next(input)) } 148 | function toss(err) { decode(gen.throw(err)) } 149 | function decode(output) { 150 | if (output.done) return ok(output.value) 151 | const op = output.value 152 | if (isPromise(op)) op.then(bounce, toss) 153 | if (op instanceof Channel) op.take(gen).then(bounce) 154 | if (op instanceof Put) op.channel.put(gen, op.value).then(bounce) 155 | if (op instanceof Race) { 156 | const state = {finished: false} 157 | for (let op of op.ops) { 158 | if (isPromise(op)) { 159 | op.then(v => { if (!state.finished) { state.finished = true; bounce(v) } }, 160 | e => { if (!state.finished) { state.finished = true; toss(e) } }) 161 | } 162 | if (op instanceof Channel) op.take(state).then(bounce) 163 | if (op instanceof Put) op.channel.put(state, op.value).then(bounce) 164 | } 165 | } 166 | } 167 | }) 168 | } 169 | 170 | it('works', () => { 171 | const ch = new Channel 172 | pogo(function*() { 173 | for (let i = 0; i < 10; i++) yield put(ch, [i, 'tick']) 174 | }) 175 | pogo(function*() { 176 | for (let i = 0; i < 10; i++) yield put(ch, [i, 'tock']) 177 | }) 178 | return pogo(function*() { 179 | for (let i = 0; i < 10; i++) { 180 | assert.deepEqual(yield ch, [i, 'tick']) 181 | assert.deepEqual(yield ch, [i, 'tock']) 182 | } 183 | }) 184 | }) 185 | }) 186 | 187 | describe('a generator example', () => { 188 | function* teammate(name) { 189 | console.log(`Hi there! I'm ${name} and I'm your teammate.`) 190 | console.log("I'll let you know if I have any blockers.") 191 | const num = yield "I'm blocked! I need a number." 192 | const str = yield "I'm blocked! I need a string." 193 | const num2 = yield "I'm blocked! I need another number." 194 | return `Ok, here's my work: ${[str, num * num2]}` 195 | } 196 | 197 | it('works', () => { 198 | const alice = teammate('Alice') 199 | assert.equal(alice.next().value, "I'm blocked! I need a number."); 200 | assert.equal(alice.next().value, "I'm blocked! I need a string."); 201 | }) 202 | }) 203 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { assert } from 'chai' 4 | import pogo, { chan, put, race, ringBuffer, strictBuffer } from '../src/index.js' 5 | 6 | const wtf = x => console.log('wtf', x) 7 | const sleep = ms => new Promise(awaken => setTimeout(awaken, ms)) 8 | 9 | describe('returning a value', () => { 10 | it("resolves the pogo's promise", () => { 11 | return pogo(function*() { 12 | return 123 13 | }).then(v => assert.equal(v, 123)) }) 14 | }) 15 | 16 | describe('raising an exception', () => { 17 | it("rejects the pogo's promise", () => { 18 | return pogo(function*() { 19 | throw 'bang!' 20 | }).catch(e => assert.equal(e, 'bang!')) 21 | }) 22 | it("rejects the pogo's promise even after a yield", () => { 23 | return pogo(function*() { 24 | const msg = yield Promise.resolve('bang') 25 | let exclamation 26 | try { yield Promise.reject('!') } 27 | catch (e) { exclamation = e } 28 | throw msg + exclamation 29 | }).catch(e => assert.equal(e, 'bang!')) 30 | }) 31 | }) 32 | 33 | describe('yielding a promise', () => { 34 | describe('that resolves', () => { 35 | it('resumes the pogo with the resolution', () => { 36 | return pogo(function*() { 37 | assert.equal(yield Promise.resolve(123), 123) 38 | }) 39 | }) 40 | }) 41 | 42 | describe('that rejects', () => { 43 | it('throws the rejection back into the pogo', () => { 44 | return pogo(function*() { 45 | try { yield Promise.reject('bang!') } 46 | catch (e) { assert.equal(e, 'bang!') } 47 | }) 48 | }) 49 | }) 50 | }) 51 | 52 | describe('yielding a put or a take', () => { 53 | it('resumes the pogo with a corresponding take or put', () => { 54 | const ch = chan() 55 | pogo(function*() { 56 | for (let i = 0; i < 10; i++) { 57 | yield put(ch, i) 58 | } 59 | }) 60 | return pogo(function*() { 61 | for (let i = 0; i < 10; i++) { 62 | assert.equal(i, yield ch) 63 | } 64 | }) 65 | }) 66 | }) 67 | 68 | describe('race', () => { 69 | it('works with promises', () => { 70 | return pogo(function*() { 71 | const {winner, value} = yield race([sleep(1000), Promise.resolve('woot')]) 72 | assert.equal(value, 'woot') 73 | }) 74 | }) 75 | 76 | it('cancels slow alternatives', () => { 77 | const log = [] 78 | const ch1 = chan() 79 | const ch2 = chan() 80 | 81 | pogo(function*() { 82 | yield race([put(ch1, 'first'), put(ch2, 'second')]) 83 | log.push('put first') 84 | yield put(ch2, 'deuxieme') 85 | log.push('put deuxieme') 86 | }).catch(wtf) 87 | 88 | return pogo(function*() { 89 | var {winner, value} = yield race([ch1, ch2]) 90 | log.push('alted') 91 | assert.equal(winner, ch1) 92 | assert.equal(value, 'first') 93 | 94 | var {winner, value} = yield race([ch1, ch2]) 95 | log.push('alted') 96 | assert.equal(winner, ch2) 97 | assert.equal(value, 'deuxieme') 98 | 99 | assert.deepEqual(log, ['put first', 'alted', 'put deuxieme', 'alted']) 100 | }) 101 | }) 102 | }) 103 | 104 | describe('yielding a generator function', () => { 105 | it('works', () => { 106 | function* example(ch) { 107 | const x = yield Promise.resolve(123) 108 | 109 | let y 110 | try { yield Promise.reject('bang!') } 111 | catch (e) { y = 456 } 112 | 113 | const z = yield ch 114 | 115 | return [x, y, z] 116 | } 117 | 118 | return pogo(function*() { 119 | const ch = chan() 120 | pogo(function*() { 121 | yield put(ch, 'sup') 122 | }) 123 | 124 | assert.deepEqual(yield example(ch), [123, 456, 'sup']) 125 | }) 126 | }) 127 | 128 | it('is safe even if you yield the same generator multiple times', () => { 129 | function* star(x) { 130 | const y = yield Promise.resolve('foo') 131 | const z = yield Promise.resolve('bar') 132 | return [x, y, z] 133 | } 134 | return pogo(function*() { 135 | const gen = star(123) 136 | assert.equal(123, (yield race([gen, Promise.resolve(123)])).value) 137 | assert.equal(456, (yield race([gen, Promise.resolve(456)])).value) 138 | assert.deepEqual((yield race([gen, Promise.resolve(789)])).value, [123, 'foo', 'bar']) 139 | }) 140 | }) 141 | }) 142 | 143 | describe('async/callback-based interface', () => { 144 | describe('putAsync', () => { 145 | it('works', (done) => { 146 | const ch = chan() 147 | pogo(function*() { 148 | assert.equal(yield ch, 123) 149 | }) 150 | ch.putAsync(123, done) 151 | }) 152 | }) 153 | 154 | describe('takeAsync', () => { 155 | it('works', (done) => { 156 | const ch = chan() 157 | pogo(function*() { 158 | yield put(ch, 123) 159 | }) 160 | ch.takeAsync(v => { 161 | assert.equal(v, 123) 162 | return done() 163 | }) 164 | }) 165 | }) 166 | }) 167 | 168 | describe('sharing a channel between multiple pogos', () => { 169 | it('works', () => { 170 | const log = [] 171 | const ch = chan() 172 | pogo(function*() { 173 | yield put(ch, 'foo') 174 | log.push('put foo') 175 | }).catch(wtf) 176 | pogo(function*() { 177 | yield put(ch, 'bar') 178 | log.push('put bar') 179 | }).catch(wtf) 180 | pogo(function*() { 181 | yield put(ch, 'baz') 182 | log.push('put baz') 183 | }).catch(wtf) 184 | return pogo(function*() { 185 | assert.equal(yield ch, 'foo') 186 | log.push('took foo') 187 | assert.equal(yield ch, 'bar') 188 | log.push('took bar') 189 | assert.equal(yield ch, 'baz') 190 | log.push('took baz') 191 | assert.deepEqual(log, [ 192 | 'put foo', 'took foo', 'put bar', 'took bar', 'put baz', 'took baz' 193 | ]) 194 | }) 195 | }) 196 | }) 197 | 198 | describe('puting and taking from the same channel', () => { 199 | it('works', () => { 200 | const log = [] 201 | const ch = chan() 202 | 203 | pogo(function*() { 204 | const ping = yield ch 205 | log.push('received ' + ping) 206 | yield put(ch, 'pong') 207 | log.push('sent pong') 208 | }).catch(wtf) 209 | 210 | return pogo(function*() { 211 | yield put(ch, 'ping') 212 | log.push('sent ping') 213 | const pong = yield ch 214 | log.push('received ' + pong) 215 | 216 | assert.deepEqual(log, ['received ping', 'sent ping', 'sent pong', 'received pong']) 217 | }) 218 | }) 219 | }) 220 | 221 | describe('pogos within pogos', () => { 222 | it('works', (done) => { 223 | return pogo(function*() { 224 | const ch = chan() 225 | pogo(function*() { 226 | for (let i = 0; i < 10; i++) { 227 | yield put(ch, i) 228 | } 229 | }).catch(done) 230 | pogo(function*() { 231 | for (let i = 0; i < 10; i++) { 232 | assert.equal(yield ch, i) 233 | } 234 | done() 235 | }).catch(done) 236 | }) 237 | }) 238 | }) 239 | 240 | describe('fairness', () => { 241 | it('works for a take and then a put', () => { 242 | const log = [] 243 | const ch = chan() 244 | pogo(function*() { 245 | yield ch 246 | log.push('took') 247 | }) 248 | return pogo(function*() { 249 | yield put(ch) 250 | log.push('put') 251 | }).then(() => assert.deepEqual(log, ['took', 'put'])) 252 | }) 253 | 254 | it('works for a put and then a take', () => { 255 | const log = [] 256 | const ch = chan() 257 | pogo(function*() { 258 | yield put(ch) 259 | log.push('put') 260 | }) 261 | return pogo(function*() { 262 | yield ch 263 | log.push('took') 264 | }).then(() => assert.deepEqual(log, ['put', 'took'])) 265 | }) 266 | }) 267 | 268 | describe('a strict buffer with non-zero capacity', () => { 269 | it('lets you successfully put n times without any takers', () => { 270 | const ch = chan(strictBuffer(2)) 271 | return pogo(function*() { 272 | yield put(ch, 1) 273 | yield put(ch, 2) 274 | assert.ok('we made it!') 275 | }) 276 | }) 277 | 278 | it('makes puters wait after the nth put though', () => { 279 | const ch = chan(strictBuffer(2)) 280 | return pogo(function*() { 281 | yield put(ch, 1) 282 | yield put(ch, 2) 283 | const {value} = yield race([sleep(10), put(ch, 3)]) 284 | assert.equal(value, undefined) 285 | }) 286 | }) 287 | 288 | it("resumes pending puters once there's room in the buffer", (done) => { 289 | const ch = chan(strictBuffer(1)) 290 | const log = [] 291 | pogo(function*(){ 292 | yield sleep(10) 293 | assert.equal(1, yield ch) 294 | log.push('took 1') 295 | assert.deepEqual(log, ['put 1', 'put 2', 'took 1']) 296 | }).then(done, done) 297 | pogo(function*() { 298 | yield put(ch, 1) 299 | log.push('put 1') 300 | yield put(ch, 2) 301 | log.push('put 2') 302 | assert.deepEqual(log, ['put 1', 'put 2']) 303 | }).catch(done) 304 | }) 305 | }) 306 | 307 | describe('a ring buffer', () => { 308 | it('drops old puts', () => { 309 | const ch = chan(ringBuffer(1)) 310 | return pogo(function*() { 311 | yield put(ch, 1) 312 | yield put(ch, 2) 313 | yield put(ch, 3) 314 | assert.equal(yield ch, 3) 315 | }) 316 | }) 317 | }) 318 | --------------------------------------------------------------------------------