Skip to content

Commit 0100f8f

Browse files
committed
alterntive approach to shorten reference chains
Store `.ref`erences on the `Action`s (now generalised as `Handle`s) instead of directly on the `Future`s. That way, when a handle is passed down `near()` to the eventual resolution, even the reference from the outermost future is only two hops (`.handle.ref`) instead of many.
1 parent d6a25c5 commit 0100f8f

File tree

5 files changed

+111
-43
lines changed

5 files changed

+111
-43
lines changed

src/Action.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
export default class Action {
1+
import Handle from './Handle'
2+
3+
export default class Action extends Handle {
24
constructor (promise) {
5+
super(null)
36
this.promise = promise
47
}
58

@@ -25,4 +28,9 @@ export default class Action {
2528
} // else
2629
this.handle(result)
2730
}
31+
32+
run () {
33+
this.ref._runAction(this)
34+
super.run()
35+
}
2836
}

src/Handle.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export default class Handle {
2+
constructor (ref) {
3+
this.ref = ref
4+
this.length = 0
5+
}
6+
near () {
7+
if (this.ref.handle !== this) {
8+
this.ref = this.ref.near()
9+
}
10+
return this.ref
11+
}
12+
_add (action) {
13+
this[this.length++] = action
14+
// potential for flattening the tree here
15+
return this
16+
}
17+
run () {
18+
for (let i = 0; i < this.length; ++i) {
19+
this.ref._runAction(this[i])
20+
this[i] = void 0
21+
}
22+
this.length = 0
23+
}
24+
}

src/Promise.js

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import makeEmitError from './emitError'
66
import maybeThenable from './maybeThenable'
77
import { PENDING, FULFILLED, REJECTED, NEVER } from './state'
88
import { isNever, isSettled } from './inspect'
9+
import Handle from './Handle'
910

1011
import then from './then'
1112
import map from './map'
@@ -51,9 +52,7 @@ class Core {
5152
export class Future extends Core {
5253
constructor () {
5354
super()
54-
this.ref = void 0
55-
this.action = void 0
56-
this.length = 0
55+
this.handle = void 0
5756
}
5857

5958
// then :: Promise e a -> (a -> b) -> Promise e b
@@ -113,77 +112,82 @@ export class Future extends Core {
113112

114113
// near :: Promise e a -> Promise e a
115114
near () {
116-
if (!this._isResolved()) {
115+
if (this.handle === void 0) {
117116
return this
118117
}
119-
120-
this.ref = this.ref.near()
121-
return this.ref
118+
return this.handle.near()
122119
}
123120

124121
// state :: Promise e a -> Int
125122
state () {
126-
return this._isResolved() ? this.ref.near().state() : PENDING
123+
// return this._isResolved() ? this.handle.near().state() : PENDING
124+
var n = this.near()
125+
return n === this ? PENDING : n.state()
127126
}
128127

129128
_isResolved () {
130-
return this.ref !== void 0
129+
return this.near() !== this
131130
}
132131

133132
_when (action) {
134133
this._runAction(action)
135134
}
136135

137136
_runAction (action) {
138-
if (this.action === void 0) {
139-
this.action = action
137+
if (this.handle) {
138+
this.handle._add(action)
139+
} else if (action.ref != null && action.ref !== this) {
140+
this.handle = new Handle(this)
141+
this.handle._add(action)
140142
} else {
141-
this[this.length++] = action
143+
this.handle = action
144+
this.handle.ref = this
142145
}
143146
}
144147

145148
_resolve (x) {
146-
this._become(resolve(x))
147-
}
148-
149-
_fulfill (x) {
150-
this._become(new Fulfilled(x))
151-
}
152-
153-
_reject (e) {
149+
x = resolve(x)
154150
if (this._isResolved()) {
155151
return
156152
}
157-
158-
this.__become(new Rejected(e))
153+
this._become(x)
159154
}
160155

161-
_become (p) {
156+
_fulfill (x) {
162157
if (this._isResolved()) {
163158
return
164159
}
165-
166-
this.__become(p)
160+
this._become(new Fulfilled(x))
167161
}
168162

169-
__become (p) {
170-
this.ref = p === this ? cycle() : p
171-
172-
if (this.action === void 0) {
163+
_reject (e) {
164+
if (this._isResolved()) {
173165
return
174166
}
175-
176-
taskQueue.add(this)
167+
this._become(new Rejected(e))
177168
}
178169

179-
run () {
180-
const p = this.ref.near()
181-
p._runAction(this.action)
182-
this.action = void 0
170+
_become (p) {
171+
if (p === this) {
172+
p = cycle()
173+
}
183174

184-
for (let i = 0; i < this.length; ++i) {
185-
p._runAction(this[i])
186-
this[i] = void 0
175+
if (this.handle) {
176+
// assert: this.handle.ref === this
177+
this.handle.ref = p
178+
if (isSettled(p)) {
179+
taskQueue.add(this.handle)
180+
} else {
181+
p._runAction(this.handle)
182+
}
183+
} else {
184+
if (isSettled(p)) {
185+
// for not unnecessarily creating handles that never see any actions
186+
// works well because it has a near() method
187+
this.handle = p
188+
} else {
189+
this.handle = new Handle(p)
190+
}
187191
}
188192
}
189193
}

src/coroutine.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import Action from './Action'
44

55
export default function (resolve, iterator, promise) {
6-
new Coroutine(resolve, iterator, promise).run()
7-
// taskQueue.add(new Coroutine(resolve, iterator, promise))
6+
new Coroutine(resolve, iterator, promise).start()
7+
// taskQueue.add(new Coroutine(resolve, iterator, promise)) // with start for run
8+
// resolve(undefined)._when(new Coroutine(resolve, iterator, promise))
89
return promise
910
}
1011

@@ -16,7 +17,7 @@ class Coroutine extends Action {
1617
this.throw = iterator.throw.bind(iterator)
1718
}
1819

19-
run () {
20+
start () {
2021
this.tryCall(this.next, void 0)
2122
}
2223

test/coroutine-test.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,44 @@ describe('coroutine', function () {
3434
.then(x => assert.strictEqual(x, expected))
3535
})
3636

37+
it('should fulfill on return', () => {
38+
const expected = {}
39+
const f = coroutine(function *(a) {
40+
return a
41+
})
42+
43+
return f(expected)
44+
.then(x => assert.strictEqual(x, expected))
45+
})
46+
3747
it('should reject on uncaught exception', () => {
3848
const expected = new Error()
3949
const f = coroutine(function *(a) {
40-
yield reject(a)
50+
throw a
4151
})
4252

4353
return f(expected)
4454
.then(assert.ifError, e => assert.strictEqual(e, expected))
4555
})
56+
57+
it('should be able to wait for the same promise multiple times', () => {
58+
const f = coroutine(function *(a) {
59+
var p = fulfill(a)
60+
return (yield p) + (yield p)
61+
})
62+
63+
return f('a').then(x => assert.equal(x, 'aa'))
64+
})
65+
66+
it('should be able to loop multiple promises', () => {
67+
const f = coroutine(function *(a) {
68+
var arr = []
69+
for (let i=0; i<5; i++) {
70+
arr.push(yield delay(1, i))
71+
}
72+
return arr
73+
})
74+
75+
return f().then(x => assert.deepEqual(x, [0, 1, 2, 3, 4]))
76+
})
4677
})

0 commit comments

Comments
 (0)