-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A complete refactor using promises instead of thunks with other new features and API changes. #18
base: master
Are you sure you want to change the base?
Changes from 5 commits
606cfec
1884c99
80010f8
6019523
eee2023
33131c3
c78fb14
361362a
16db99b
c0a3b6f
23ead8d
1dd1ea1
5de85a6
fc009a7
bf8a2ba
7fe424f
0a15b13
2a0874a
d341e54
5a23d57
5ed2d30
251e216
d33b344
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
export class BufferBase { | ||
values = [] | ||
|
||
constructor (size) { | ||
this.size = parseInt(size, 10) | ||
} | ||
|
||
shift () { | ||
return this.values.shift() | ||
} | ||
} | ||
|
||
export class BufferBlocking extends BufferBase { | ||
push (getValue) { | ||
if (this.values.length < this.size) { | ||
this.values.push(getValue()) | ||
return true | ||
} | ||
return false | ||
} | ||
} | ||
|
||
export class BufferDropping extends BufferBase { | ||
push (getValue) { | ||
const value = getValue() | ||
if (this.values.length < this.size) { | ||
this.values.push(value) | ||
} | ||
return true | ||
} | ||
} | ||
|
||
export class BufferSliding extends BufferBase { | ||
push (getValue) { | ||
this.values.push(getValue()) | ||
if (this.values.length > this.size) { | ||
this.values.shift() | ||
} | ||
return true | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import {default as Deferred, DeferredPut} from './deferred' | ||
|
||
const CLOSED_ERROR_MSG = 'Cannot add to closed channel' | ||
|
||
export default class Channel { | ||
pendingPuts = [] | ||
pendingTakes = [] | ||
isClosed = false | ||
isDone = false | ||
empty = {} | ||
|
||
constructor (buffer) { | ||
this.buffer = buffer | ||
} | ||
|
||
then (onFulfilled, onRejected) { | ||
return this.take().then(onFulfilled, onRejected) | ||
} | ||
|
||
take () { | ||
const deferred = new Deferred() | ||
if (this.done()) { | ||
this.resolveEmpty(deferred) | ||
} else if (this.hasValues()) { | ||
this.resolve(deferred, this.nextValue()) | ||
} else { | ||
this.pendingTakes.push(deferred) | ||
} | ||
return deferred.promise | ||
} | ||
|
||
cancelableTake () { | ||
const promise = this.take() | ||
return [ | ||
promise, | ||
() => this.removePendingTake(promise.deferred) | ||
] | ||
} | ||
|
||
removePendingTake (deferred) { | ||
const idx = this.pendingTakes.indexOf(deferred) | ||
if (idx > -1) { | ||
this.pendingTakes.splice(idx, 1) | ||
} | ||
} | ||
|
||
hasValues () { | ||
return this.buffer.length > 0 || this.pendingPuts.length > 0 | ||
} | ||
|
||
nextValue () { | ||
if (this.pendingPuts.length > 0) { | ||
this.buffer.push(this.pendingPuts.shift().put()) | ||
} | ||
return this.buffer.shift() | ||
} | ||
|
||
put (value) { | ||
var deferred = new DeferredPut(value) | ||
if (this.isClosed) { | ||
deferred.reject(new Error(CLOSED_ERROR_MSG)) | ||
} else if (this.pendingTakes.length > 0) { | ||
this.resolve(this.pendingTakes.shift(), deferred.put()) | ||
} else if (!this.buffer.push(deferred.put.bind(deferred))) { | ||
this.pendingPuts.push(deferred) | ||
} | ||
return deferred.promise | ||
} | ||
|
||
resolve (deferred, value) { | ||
deferred.resolve(value) | ||
this.done() | ||
} | ||
|
||
resolveEmpty (deferred) { | ||
this.resolve(deferred, this.empty) | ||
} | ||
|
||
close () { | ||
this.isClosed = true | ||
let receiver | ||
while (receiver = this.pendingPuts.shift()) { | ||
receiver.error(new Error(CLOSED_ERROR_MSG)) | ||
} | ||
return this.done() | ||
} | ||
|
||
done () { | ||
if (!this.isDone && this.isClosed && this.buffer.length === 0) { | ||
this.isDone = true | ||
this.pendingTakes.forEach(this.resolveEmpty, this) | ||
} | ||
return this.isDone | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export default class Deferred { | ||
constructor () { | ||
this.promise = new Promise((resolve, reject) => { | ||
this.resolve = resolve | ||
this.reject = reject | ||
}) | ||
this.promise.deferred = this | ||
} | ||
} | ||
|
||
export class DeferredPut extends Deferred { | ||
constructor (value) { | ||
super() | ||
this.value = value | ||
} | ||
|
||
put () { | ||
this.resolve() | ||
return this.value | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import Channel from './channel' | ||
import {BufferBlocking, BufferSliding, BufferDropping} from './buffer' | ||
|
||
export function blockingChannel (size) { | ||
return new Channel(new BufferBlocking(size)) | ||
} | ||
|
||
export function slidingChannel (size) { | ||
return new Channel(new BufferSliding(size)) | ||
} | ||
|
||
export function droppingChannel (size) { | ||
return new Channel(new BufferDropping(size)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import {blockingChannel, slidingChannel, droppingChannel} from './factory' | ||
import Channel from './channel' | ||
import timeout from './timeout' | ||
import alts from './alts' | ||
|
||
const chan = blockingChannel | ||
chan.sliding = slidingChannel | ||
chan.dropping = droppingChannel | ||
chan.Channel = Channel | ||
chan.timeout = timeout | ||
chan.alts = alts | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be select, considered renaming, but I think it is better to keep go terminology since there is aleady a js library similar to |
||
|
||
export default chan |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import Deferred from './deferred' | ||
|
||
function pairs () | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oops, shouldn't have commited this line :(, thats what I get for just commiting stuff in a half complete state. |
||
|
||
export function select (channels...) { | ||
const deferred = new Deferred() | ||
const nonEmpty = channels.filter(channel => channel.hasValues()) | ||
const cancels = [] | ||
let remaining = channels.length | ||
|
||
const take = channel => { | ||
const [promise, cancel] = channel.cancelableTake() | ||
cancels.push(cancel) | ||
promise.then(value => { | ||
if (value === channel.empty && --remaining > 0) { | ||
return | ||
} | ||
cancels.forEach(fn => fn()) | ||
deferred.resolve([channel, value]) | ||
}) | ||
} | ||
|
||
if (nonEmpty.length > 1) { | ||
take(nonEmpty[Math.random() * nonEmpty.length | 0]) | ||
} else { | ||
channels.forEach(take) | ||
} | ||
|
||
return deferred.promise | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import {blockingChannel} from './factory' | ||
|
||
export default function timeout (ms) { | ||
const ch = blockingChannel() | ||
setTimeout(() => { | ||
try { | ||
ch.put(true) | ||
ch.close() | ||
} catch (err) {} | ||
}, ms) | ||
return ch | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was on a kick where I really liked
tape
when I wrote this, but I am good with sticking withmocha