Skip to content

Commit b4a5a36

Browse files
committed
Adding a fix for issue facebook#17
1 parent fb08ab3 commit b4a5a36

2 files changed

Lines changed: 154 additions & 110 deletions

File tree

src/renderers/dom/server/ReactServerAsyncRendering.js

Lines changed: 83 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ var isReadableStream = require('isReadableStream');
2828
var rollingAdler32 = require("rollingAdler32");
2929
var stream = require("stream");
3030

31-
// this is a pass through stream that can calculate the hash that is used to
31+
// this is a pass through stream that can calculate the hash that is used to
3232
// checksum react server-rendered elements.
3333
class Adler32Stream extends stream.Transform {
3434
constructor(rootId, options) {
@@ -73,84 +73,88 @@ class RenderStream extends stream.Readable {
7373
}
7474

7575
_read(n) {
76-
var bufferToPush;
77-
if (this.done) {
78-
this.push(null);
79-
return;
80-
}
81-
// it's possible that the last chunk added bumped the buffer up to > 2 * n, which means we will
82-
// need to go through multiple read calls to drain it down to < n.
83-
if (this.buffer.length >= n) {
84-
bufferToPush = this.buffer.substring(0, n);
85-
this.buffer = this.buffer.substring(n);
86-
this.push(bufferToPush);
87-
return;
88-
}
89-
90-
if (this.stream) {
91-
let data = this.stream.read(n);
92-
// if the underlying stream isn't ready, it returns null, so we push a blank string to
93-
// get it to work.
94-
if (null === data) {
95-
this.push("");
96-
} else {
97-
this.push(data);
76+
try {
77+
var bufferToPush;
78+
if (this.done) {
79+
this.push(null);
80+
return;
81+
}
82+
// it's possible that the last chunk added bumped the buffer up to > 2 * n, which means we will
83+
// need to go through multiple read calls to drain it down to < n.
84+
if (this.buffer.length >= n) {
85+
bufferToPush = this.buffer.substring(0, n);
86+
this.buffer = this.buffer.substring(n);
87+
this.push(bufferToPush);
88+
return;
9889
}
99-
return;
100-
}
101-
// if we have are already rendering and have a continuation to call, do so.
102-
if (this.continuation) {
103-
// continue with the rendering.
104-
this.continuation();
105-
return;
106-
}
10790

108-
this.stackDepth = 0;
109-
// start the rendering chain.
110-
this.componentInstance.mountComponentAsync(this.id, this.transaction, this.context,
111-
(text, cb) => {
112-
if (isReadableStream(text)) {
113-
// this is a stream
114-
this.stream = text;
115-
this.stream.on("end", () => {
116-
this.stream = null;
117-
cb();
118-
});
119-
let data = this.stream.read(n - this.buffer.length);
120-
121-
setImmediate(() => {
122-
if (data === null) data = this.stream.read(n - this.buffer.length);
123-
this.push(this.buffer + (data === null ? "" : data));
124-
this.buffer = "";
125-
});
126-
return;
91+
if (this.stream) {
92+
let data = this.stream.read(n);
93+
// if the underlying stream isn't ready, it returns null, so we push a blank string to
94+
// get it to work.
95+
if (null === data) {
96+
this.push("");
97+
} else {
98+
this.push(data);
12799
}
100+
return;
101+
}
102+
// if we have are already rendering and have a continuation to call, do so.
103+
if (this.continuation) {
104+
// continue with the rendering.
105+
this.continuation();
106+
return;
107+
}
128108

129-
this.buffer += text;
130-
if (this.buffer.length >= n) {
131-
this.continuation = cb;
132-
bufferToPush = this.buffer.substring(0, n);
133-
this.buffer = this.buffer.substring(n);
134-
this.push(bufferToPush);
135-
} else {
136-
// continue rendering until we have enough text to call this.push().
137-
// sometimes do this as process.nextTick to get out of stack overflows.
138-
if (this.stackDepth >= this.maxStackDepth) {
139-
process.nextTick(cb);
140-
} else {
141-
this.stackDepth++;
142-
cb();
143-
this.stackDepth--;
109+
this.stackDepth = 0;
110+
// start the rendering chain.
111+
this.componentInstance.mountComponentAsync(this.id, this.transaction, this.context,
112+
(text, cb) => {
113+
if (isReadableStream(text)) {
114+
// this is a stream
115+
this.stream = text;
116+
this.stream.on("end", () => {
117+
this.stream = null;
118+
cb();
119+
});
120+
let data = this.stream.read(n - this.buffer.length);
121+
122+
setImmediate(() => {
123+
if (data === null) data = this.stream.read(n - this.buffer.length);
124+
this.push(this.buffer + (data === null ? "" : data));
125+
this.buffer = "";
126+
});
127+
return;
144128
}
145-
}
146-
},
147-
this.cache,
148-
() => {
149-
// the rendering is finished; we should push out the last of the buffer.
150-
this.done = true;
151-
this.push(this.buffer);
152-
})
153129

130+
this.buffer += text;
131+
if (this.buffer.length >= n) {
132+
this.continuation = cb;
133+
bufferToPush = this.buffer.substring(0, n);
134+
this.buffer = this.buffer.substring(n);
135+
this.push(bufferToPush);
136+
} else {
137+
// continue rendering until we have enough text to call this.push().
138+
// sometimes do this as process.nextTick to get out of stack overflows.
139+
if (this.stackDepth >= this.maxStackDepth) {
140+
process.nextTick(cb);
141+
} else {
142+
this.stackDepth++;
143+
cb();
144+
this.stackDepth--;
145+
}
146+
}
147+
},
148+
this.cache,
149+
() => {
150+
// the rendering is finished; we should push out the last of the buffer.
151+
this.done = true;
152+
this.push(this.buffer);
153+
});
154+
} catch (e) {
155+
this.emit('error', e);
156+
return;
157+
}
154158
}
155159
}
156160

@@ -173,20 +177,23 @@ function renderToStringStream(element, {syncBatching = false, cache, rootID} = {
173177
var id = rootID || ReactInstanceHandles.createReactRootID();
174178
transaction = ReactServerRenderingTransaction.getPooled(false);
175179

176-
var readable = transaction.perform(function() {
180+
var markupStream = transaction.perform(function() {
177181
var componentInstance = instantiateReactComponent(element, null);
178182
return new RenderStream(componentInstance, id, transaction, emptyObject, cache);
179183
}, null);
180184

181-
readable.on("end", () => {
185+
markupStream.on("end", () => {
182186
ReactServerRenderingTransaction.release(transaction);
183187
// Revert to the DOM batching strategy since these two renderers
184188
// currently share these stateful modules.
185189
// NOTE: THIS SHOULD ONLY BE DONE IN TESTS OR OTHER ENVIRONMENTS KNOWN TO BE SYNCHRONOUS.
186190
if (syncBatching) ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy);
187191
});
188192

189-
return readable.pipe(new Adler32Stream(id));
193+
var checksumedStream = markupStream.pipe(new Adler32Stream(id));
194+
// manually propagate errors down the chain.
195+
markupStream.on('error', (e) => checksumedStream.emit('error', e));
196+
return checksumedStream;
190197
}
191198

192199
/**

0 commit comments

Comments
 (0)