Skip to content

Commit 2904af4

Browse files
support possibly synchronous generators
1 parent a376993 commit 2904af4

File tree

2 files changed

+72
-32
lines changed

2 files changed

+72
-32
lines changed

modules/Coroutine.js

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,26 @@
11
import { Component } from 'react';
22
import isEqual from 'shallowequal';
33

4-
export default class Coroutine extends Component {
5-
static create(asyncFn) {
6-
return class AsyncComponent extends Coroutine {
7-
static get contextTypes() {
8-
return asyncFn.contextTypes;
9-
}
10-
11-
static get displayName() {
12-
return `Coroutine(${asyncFn.name})`;
13-
}
14-
15-
observe(props, context) {
16-
return asyncFn(props, context);
17-
}
4+
export default { create };
185

19-
render() {
20-
return this.state.data;
21-
}
6+
function create(asyncFn) {
7+
let displayName = asyncFn.name || asyncFn.displayName || 'Anonymous';
8+
return class extends Coroutine {
9+
static get displayName() {
10+
return `Coroutine(${displayName})`;
2211
}
23-
}
2412

25-
static get displayName() {
26-
return `Coroutine(${this.name})`;
13+
observe(props) {
14+
return asyncFn(props);
15+
}
2716
}
17+
}
2818

29-
constructor(props, context) {
30-
super(props, context);
19+
class Coroutine extends Component {
20+
constructor(props) {
21+
super(props);
3122
this.state = { data: null };
3223
this.iterator = null;
33-
this.forceUpdateHelper = this.forceUpdate.bind(this);
3424
this.isComponentMounted = false;
3525
}
3626

@@ -50,21 +40,43 @@ export default class Coroutine extends Component {
5040
}
5141
});
5242
} else {
53-
const getNextBody = () => {
54-
this.iterator.next().then((data) => {
55-
if (!this.isComponentMounted || this.iterator !== asyncBody) {
56-
return;
43+
function resolveSyncIterator(i, step, cb) {
44+
if (!step.done) {
45+
if (step.value && typeof step.value.then === 'function') {
46+
step.value.then(data => resolveSyncIterator(i, i.next(data), cb));
47+
} else {
48+
resolveSyncIterator(i, i.next(step.value), cb);
5749
}
50+
} else {
51+
cb(step.value);
52+
}
53+
};
5854

55+
function resolveAsyncIterator(i, step, cb) {
56+
step.then((data) => {
5957
if (data.value !== undefined) {
60-
this.setState(() => ({ data: data.value }));
58+
cb(data.value);
6159
}
6260

63-
return !data.done && getNextBody();
61+
return !data.done && resolveAsyncIterator(i, i.next(), cb);
6462
});
6563
};
6664

67-
getNextBody();
65+
function resolveIterator(iterator, instance) {
66+
const step = iterator.next();
67+
68+
const updater = (data) => {
69+
return instance.isComponentMounted && instance.setState({ data });
70+
};
71+
72+
if (typeof step.then === 'function') {
73+
resolveAsyncIterator(iterator, step, updater);
74+
} else {
75+
resolveSyncIterator(iterator, step, updater);
76+
}
77+
};
78+
79+
resolveIterator(this.iterator, this);
6880
}
6981
}
7082

@@ -94,6 +106,6 @@ export default class Coroutine extends Component {
94106
}
95107

96108
render() {
97-
throw new Error('Coroutine::render should be implemented by a subclass');
109+
return this.state.data;
98110
}
99111
}

tests/Coroutine.test.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import Renderer from 'react-test-renderer';
33
import Coroutine from '../modules/Coroutine';
44

5-
describe('Coroutine', async () => {
5+
describe('Coroutine', () => {
66
it('should render empty body until coroutine is resolved', async () => {
77
async function render() {
88
return <p>test</p>;
@@ -33,4 +33,32 @@ describe('Coroutine', async () => {
3333
const second = await Renderer.create(<p>Done!</p>);
3434
expect(tree.toJSON()).toEqual(second.toJSON());
3535
});
36+
37+
it('should render generator in sync mode', () => {
38+
function* render() {
39+
let a = yield 1;
40+
let b = yield 2;
41+
return <p>{a}, {b}</p>;
42+
}
43+
44+
let TestComponent = Coroutine.create(render);
45+
let tree = Renderer.create(<TestComponent />);
46+
47+
let result = Renderer.create(<p>{1}, {2}</p>);
48+
expect(tree.toJSON()).toEqual(result.toJSON());
49+
});
50+
51+
it('should render generator in async mode', async () => {
52+
function* render() {
53+
let a = yield Promise.resolve(1);
54+
let b = yield 2;
55+
return <p>{a}, {b}</p>;
56+
}
57+
58+
let TestComponent = Coroutine.create(render);
59+
let tree = Renderer.create(<TestComponent />);
60+
61+
let result = await Renderer.create(<p>{1}, {2}</p>);
62+
expect(tree.toJSON()).toEqual(result.toJSON());
63+
});
3664
});

0 commit comments

Comments
 (0)